1 /*
2  * Copyright (c) 1997 - 2001 Hansj�rg Malthaner
3  *
4  * This file is part of the Simutrans project under the artistic licence.
5  * (see licence.txt)
6  */
7 
8 #include <stdio.h>
9 #ifdef MULTI_THREAD
10 #include "../utils/simthread.h"
11 static pthread_mutex_t verbinde_mutex = PTHREAD_MUTEX_INITIALIZER;
12 static pthread_mutex_t calc_image_mutex = PTHREAD_MUTEX_INITIALIZER;
13 static pthread_mutex_t pumpe_list_mutex = PTHREAD_MUTEX_INITIALIZER;
14 static pthread_mutex_t senke_list_mutex = PTHREAD_MUTEX_INITIALIZER;
15 #endif
16 
17 #include "leitung2.h"
18 #include "../simdebug.h"
19 #include "../simworld.h"
20 #include "../simobj.h"
21 #include "../player/simplay.h"
22 #include "../display/simimg.h"
23 #include "../simfab.h"
24 #include "../simskin.h"
25 
26 #include "../display/simgraph.h"
27 
28 #include "../utils/cbuffer_t.h"
29 
30 #include "../dataobj/translator.h"
31 #include "../dataobj/loadsave.h"
32 #include "../dataobj/powernet.h"
33 #include "../dataobj/environment.h"
34 
35 #include "../boden/grund.h"
36 #include "../bauer/wegbauer.h"
37 
38 const uint32 POWER_TO_MW = 12;
39 
40 // use same precision as powernet
41 const uint8 leitung_t::FRACTION_PRECISION = powernet_t::FRACTION_PRECISION;
42 
43 /**
44  * returns possible directions for powerline on this tile
45  */
get_powerline_ribi(grund_t * gr)46 ribi_t::ribi get_powerline_ribi(grund_t *gr)
47 {
48 	slope_t::type slope = gr->get_weg_hang();
49 	ribi_t::ribi ribi = (ribi_t::ribi)ribi_t::all;
50 	if (slope == slope_t::flat) {
51 		// respect possible directions for bridge and tunnel starts
52 		if (gr->ist_karten_boden()  &&  (gr->ist_tunnel()  ||  gr->ist_bruecke())) {
53 			ribi = ribi_t::doubles( ribi_type( gr->get_grund_hang() ) );
54 		}
55 	}
56 	else {
57 		ribi = ribi_t::doubles( ribi_type(slope) );
58 	}
59 	return ribi;
60 }
61 
gimme_neighbours(leitung_t ** conn)62 int leitung_t::gimme_neighbours(leitung_t **conn)
63 {
64 	int count = 0;
65 	grund_t *gr_base = welt->lookup(get_pos());
66 	ribi_t::ribi ribi = get_powerline_ribi(gr_base);
67 	for(int i=0; i<4; i++) {
68 		// get next connected tile (if there)
69 		grund_t *gr;
70 		conn[i] = NULL;
71 		if(  (ribi & ribi_t::nsew[i])  &&  gr_base->get_neighbour( gr, invalid_wt, ribi_t::nsew[i] ) ) {
72 			leitung_t *lt = gr->get_leitung();
73 			// check that we can connect to the other tile: correct slope,
74 			// both ground or both tunnel or both not tunnel
75 			bool const ok = (gr->ist_karten_boden()  &&  gr_base->ist_karten_boden())  ||  (gr->ist_tunnel()==gr_base->ist_tunnel());
76 			if(  lt  &&  (ribi_t::backward(ribi_t::nsew[i]) & get_powerline_ribi(gr))  &&  ok  ) {
77 				const player_t *owner = get_owner();
78 				const player_t *other = lt->get_owner();
79 				const player_t *super = welt->get_public_player();
80 				if (owner==other  ||  owner==super  ||  other==super) {
81 					conn[i] = lt;
82 					count++;
83 				}
84 			}
85 		}
86 	}
87 	return count;
88 }
89 
90 
suche_fab_4(const koord pos)91 fabrik_t *leitung_t::suche_fab_4(const koord pos)
92 {
93 	for(int k=0; k<4; k++) {
94 		fabrik_t *fab = fabrik_t::get_fab( pos+koord::nsew[k] );
95 		if(fab) {
96 			return fab;
97 		}
98 	}
99 	return NULL;
100 }
101 
102 
leitung_t(loadsave_t * file)103 leitung_t::leitung_t(loadsave_t *file) : obj_t()
104 {
105 	image = IMG_EMPTY;
106 	set_net(NULL);
107 	ribi = ribi_t::none;
108 	rdwr(file);
109 }
110 
111 
leitung_t(koord3d pos,player_t * player)112 leitung_t::leitung_t(koord3d pos, player_t *player) : obj_t(pos)
113 {
114 	image = IMG_EMPTY;
115 	set_net(NULL);
116 	set_owner( player );
117 	set_desc(way_builder_t::leitung_desc);
118 }
119 
120 
~leitung_t()121 leitung_t::~leitung_t()
122 {
123 	if (welt->is_destroying()) {
124 		return;
125 	}
126 
127 	grund_t *gr = welt->lookup(get_pos());
128 	if(gr) {
129 		leitung_t *conn[4];
130 		int neighbours = gimme_neighbours(conn);
131 		gr->obj_remove(this);
132 		set_flag( obj_t::not_on_map );
133 
134 		if(neighbours>1) {
135 			// only reconnect if two connections ...
136 			bool first = true;
137 			for(int i=0; i<4; i++) {
138 				if(conn[i]!=NULL) {
139 					if(!first) {
140 						// replace both nets
141 						powernet_t *new_net = new powernet_t();
142 						conn[i]->replace(new_net);
143 					}
144 					first = false;
145 				}
146 			}
147 		}
148 
149 		// recalc images
150 		for(int i=0; i<4; i++) {
151 			if(conn[i]!=NULL) {
152 				conn[i]->calc_neighbourhood();
153 			}
154 		}
155 
156 		if(neighbours==0) {
157 			delete net;
158 		}
159 		if(!gr->ist_tunnel()) {
160 			player_t::add_maintenance(get_owner(), -desc->get_maintenance(), powerline_wt);
161 		}
162 	}
163 }
164 
165 
cleanup(player_t * player)166 void leitung_t::cleanup(player_t *player)
167 {
168 	player_t::book_construction_costs(player, -desc->get_price()/2, get_pos().get_2d(), powerline_wt);
169 	mark_image_dirty( image, 0 );
170 }
171 
172 
173 /**
174  * called during map rotation
175  * @author prissi
176  */
rotate90()177 void leitung_t::rotate90()
178 {
179 	obj_t::rotate90();
180 	ribi = ribi_t::rotate90( ribi );
181 }
182 
183 
184 /* replace networks connection
185  * non-trivial to handle transformers correctly
186  * @author prissi
187  */
replace(powernet_t * new_net)188 void leitung_t::replace(powernet_t* new_net)
189 {
190 	if (get_net() != new_net) {
191 		// convert myself ...
192 //DBG_MESSAGE("leitung_t::replace()","My net %p by %p at (%i,%i)",new_net,current,base_pos.x,base_pos.y);
193 		set_net(new_net);
194 	}
195 
196 	leitung_t * conn[4];
197 	if(gimme_neighbours(conn)>0) {
198 		for(int i=0; i<4; i++) {
199 			if(conn[i] && conn[i]->get_net()!=new_net) {
200 				conn[i]->replace(new_net);
201 			}
202 		}
203 	}
204 }
205 
206 
207 /**
208  * Connect this piece of powerline to its neighbours
209  * -> this can merge power networks
210  * @author Hj. Malthaner
211  */
verbinde()212 void leitung_t::verbinde()
213 {
214 	// first get my own ...
215 	powernet_t *new_net = get_net();
216 //DBG_MESSAGE("leitung_t::verbinde()","Searching net at (%i,%i)",get_pos().x,get_pos().x);
217 	leitung_t * conn[4];
218 	if(gimme_neighbours(conn)>0) {
219 		for( uint8 i=0;  i<4 && new_net==NULL;  i++  ) {
220 			if(conn[i]) {
221 				new_net = conn[i]->get_net();
222 			}
223 		}
224 	}
225 
226 //DBG_MESSAGE("leitung_t::verbinde()","Found net %p",new_net);
227 
228 	// we are alone?
229 	if(get_net()==NULL) {
230 		if(new_net!=NULL) {
231 			replace(new_net);
232 		}
233 		else {
234 			// then we start a new net
235 			set_net(new powernet_t());
236 //DBG_MESSAGE("leitung_t::verbinde()","Creating new net %p",new_net);
237 		}
238 	}
239 	else if(new_net) {
240 		powernet_t *my_net = get_net();
241 		for( uint8 i=0;  i<4;  i++  ) {
242 			if(conn[i] && conn[i]->get_net()!=new_net) {
243 				conn[i]->replace(new_net);
244 			}
245 		}
246 		if(my_net && my_net!=new_net) {
247 			delete my_net;
248 		}
249 	}
250 }
251 
252 
253 /* extended by prissi */
calc_image()254 void leitung_t::calc_image()
255 {
256 	is_crossing = false;
257 	const koord pos = get_pos().get_2d();
258 	bool snow = get_pos().z >= welt->get_snowline()  ||  welt->get_climate( get_pos().get_2d() ) == arctic_climate;
259 
260 	grund_t *gr = welt->lookup(get_pos());
261 	if(gr==NULL) {
262 		// no valid ground; usually happens during building ...
263 		return;
264 	}
265 	if(gr->ist_bruecke() || (gr->get_typ()==grund_t::tunnelboden && gr->ist_karten_boden())) {
266 		// don't display on a bridge or in a tunnel)
267 		set_image(IMG_EMPTY);
268 		return;
269 	}
270 
271 	image_id old_image = get_image();
272 	slope_t::type hang = gr->get_weg_hang();
273 	if(hang != slope_t::flat) {
274 		set_image( desc->get_slope_image_id(hang, snow));
275 	}
276 	else {
277 		if(gr->hat_wege()) {
278 			// crossing with road or rail
279 			weg_t* way = gr->get_weg_nr(0);
280 			if(ribi_t::is_straight_ew(way->get_ribi())) {
281 				set_image( desc->get_diagonal_image_id(ribi_t::north|ribi_t::east, snow));
282 			}
283 			else {
284 				set_image( desc->get_diagonal_image_id(ribi_t::south|ribi_t::east, snow));
285 			}
286 			is_crossing = true;
287 		}
288 		else {
289 			if(ribi_t::is_straight(ribi)  &&  !ribi_t::is_single(ribi)  &&  (pos.x+pos.y)&1) {
290 				// every second skip mast
291 				if(ribi_t::is_straight_ns(ribi)) {
292 					set_image( desc->get_diagonal_image_id(ribi_t::north|ribi_t::west, snow));
293 				}
294 				else {
295 					set_image( desc->get_diagonal_image_id(ribi_t::south|ribi_t::west, snow));
296 				}
297 			}
298 			else {
299 				set_image( desc->get_image_id(ribi, snow));
300 			}
301 		}
302 	}
303 	if (old_image != get_image()) {
304 		mark_image_dirty(old_image,0);
305 	}
306 }
307 
308 
309 /**
310  * Recalculates the images of all neighbouring
311  * powerlines and the powerline itself
312  *
313  * @author Hj. Malthaner
314  */
calc_neighbourhood()315 void leitung_t::calc_neighbourhood()
316 {
317 	leitung_t *conn[4];
318 	ribi = ribi_t::none;
319 	if(gimme_neighbours(conn)>0) {
320 		for( uint8 i=0;  i<4 ;  i++  ) {
321 			if(conn[i]  &&  conn[i]->get_net()==get_net()) {
322 				ribi |= ribi_t::nsew[i];
323 				conn[i]->add_ribi(ribi_t::backward(ribi_t::nsew[i]));
324 				conn[i]->calc_image();
325 			}
326 		}
327 	}
328 	set_flag( obj_t::dirty );
329 	calc_image();
330 }
331 
332 
333 /**
334  * @return Einen Beschreibungsstring f�r das Objekt, der z.B. in einem
335  * Beobachtungsfenster angezeigt wird.
336  * @author Hj. Malthaner
337  */
info(cbuffer_t & buf) const338 void leitung_t::info(cbuffer_t & buf) const
339 {
340 	obj_t::info(buf);
341 
342 	powernet_t * const net = get_net();
343 
344 	buf.printf(translator::translate("Net ID: %p"), net);
345 	buf.printf("\n");
346 	//buf.printf(translator::translate("Capacity: %.0f MW"), (double)(net->get_max_capacity() >> POWER_TO_MW));
347 	//buf.printf("\n");
348 	buf.printf(translator::translate("Demand: %.0f MW"), (double)(net->get_demand() >> POWER_TO_MW));
349 	buf.printf("\n");
350 	buf.printf(translator::translate("Generation: %.0f MW"), (double)(net->get_supply() >> POWER_TO_MW));
351 	buf.printf("\n");
352 	buf.printf(translator::translate("Usage: %.0f %%"), (double)((100 * net->get_normal_demand()) >> powernet_t::FRACTION_PRECISION));
353 }
354 
355 /**
356  * Wird nach dem Laden der Welt aufgerufen - �blicherweise benutzt
357  * um das Aussehen des Dings an Boden und Umgebung anzupassen
358  *
359  * @author Hj. Malthaner
360  */
finish_rd()361 void leitung_t::finish_rd()
362 {
363 #ifdef MULTI_THREAD
364 	pthread_mutex_lock( &verbinde_mutex );
365 #endif
366 	verbinde();
367 #ifdef MULTI_THREAD
368 	pthread_mutex_unlock( &verbinde_mutex );
369 #endif
370 #ifdef MULTI_THREAD
371 	pthread_mutex_lock( &calc_image_mutex );
372 #endif
373 	calc_neighbourhood();
374 #ifdef MULTI_THREAD
375 	pthread_mutex_unlock( &calc_image_mutex );
376 #endif
377 	grund_t *gr = welt->lookup(get_pos());
378 	assert(gr); (void)gr;
379 
380 	player_t::add_maintenance(get_owner(), desc->get_maintenance(), powerline_wt);
381 }
382 
rdwr(loadsave_t * file)383 void leitung_t::rdwr(loadsave_t *file)
384 {
385 	xml_tag_t d( file, "leitung_t" );
386 
387 	obj_t::rdwr(file);
388 
389 	// no longer save power net pointer as it is no longer used
390 	if(  file->is_version_less(120, 4)  ) {
391 		uint32 value = 0;
392 		file->rdwr_long(value);
393 	}
394 	if(  file->is_loading()  ) {
395 		set_net(NULL);
396 	}
397 
398 	if(get_typ()==leitung) {
399 		/* ATTENTION: during loading thus MUST not be called from the constructor!!!
400 		* (Otherwise it will be always true!
401 		*/
402 		if(file->is_version_atleast(102, 3)) {
403 			if(file->is_saving()) {
404 				const char *s = desc->get_name();
405 				file->rdwr_str(s);
406 			}
407 			else {
408 				char bname[128];
409 				file->rdwr_str(bname, lengthof(bname));
410 
411 				const way_desc_t *desc = way_builder_t::get_desc(bname);
412 				if(desc==NULL) {
413 					desc = way_builder_t::get_desc(translator::compatibility_name(bname));
414 					if(desc==NULL) {
415 						welt->add_missing_paks( bname, karte_t::MISSING_WAY );
416 						desc = way_builder_t::leitung_desc;
417 					}
418 					dbg->warning("leitung_t::rdwr()", "Unknown powerline %s replaced by %s", bname, desc->get_name() );
419 				}
420 				set_desc(desc);
421 			}
422 		}
423 		else {
424 			if (file->is_loading()) {
425 				set_desc(way_builder_t::leitung_desc);
426 			}
427 		}
428 	}
429 }
430 
431 // returns NULL, if removal is allowed
432 // players can remove public owned powerlines
is_deletable(const player_t * player)433 const char *leitung_t::is_deletable(const player_t *player)
434 {
435 	if(  get_player_nr()==welt->get_public_player()->get_player_nr()  &&  player  ) {
436 		return NULL;
437 	}
438 	return obj_t::is_deletable(player);
439 }
440 
441 
442 /************************************ from here on pump (source) stuff ********************************************/
443 
444 slist_tpl<pumpe_t *> pumpe_t::pumpe_list;
445 
446 
new_world()447 void pumpe_t::new_world()
448 {
449 	pumpe_list.clear();
450 }
451 
452 
step_all(uint32 delta_t)453 void pumpe_t::step_all(uint32 delta_t)
454 {
455 	FOR(slist_tpl<pumpe_t*>, const p, pumpe_list) {
456 		p->step(delta_t);
457 	}
458 }
459 
460 
pumpe_t(loadsave_t * file)461 pumpe_t::pumpe_t(loadsave_t *file ) : leitung_t( koord3d::invalid, NULL )
462 {
463 	fab = NULL;
464 	power_supply = 0;
465 	rdwr( file );
466 }
467 
468 
pumpe_t(koord3d pos,player_t * player)469 pumpe_t::pumpe_t(koord3d pos, player_t *player) : leitung_t(pos, player)
470 {
471 	fab = NULL;
472 	power_supply = 0;
473 	player_t::book_construction_costs(player, welt->get_settings().cst_transformer, get_pos().get_2d(), powerline_wt);
474 }
475 
476 
~pumpe_t()477 pumpe_t::~pumpe_t()
478 {
479 	if(fab) {
480 		fab->set_transformer_connected(NULL);
481 		fab = NULL;
482 	}
483 	if(  net != NULL  ) {
484 		net->sub_supply(power_supply);
485 	}
486 	pumpe_list.remove( this );
487 	player_t::add_maintenance(get_owner(), (sint32)welt->get_settings().cst_maintain_transformer, powerline_wt);
488 }
489 
step(uint32 delta_t)490 void pumpe_t::step(uint32 delta_t)
491 {
492 	if(  fab == NULL  ) {
493 		return;
494 	}
495 	else if(  delta_t == 0  ) {
496 		return;
497 	}
498 
499 	// usage logic could go here
500 
501 	// resolve image
502 	uint16 winter_offset = 0;
503 	if(  skinverwaltung_t::senke->get_count() > 3  &&  (get_pos().z >= welt->get_snowline()  ||  welt->get_climate( get_pos().get_2d() ) == arctic_climate)  ) {
504 		winter_offset = 2;
505 	}
506 	uint16 const image_offset = power_supply > 0 ? 1 : 0;
507 	image_id const new_image = skinverwaltung_t::pumpe->get_image_id(image_offset + winter_offset);
508 
509 	// update image
510 	if(  image != new_image  ) {
511 		set_flag(obj_t::dirty);
512 		set_image(new_image);
513 	}
514 }
515 
set_net(powernet_t * p)516 void pumpe_t::set_net(powernet_t * p)
517 {
518 	powernet_t * p_old = get_net();
519 	if(  p_old != NULL  ) {
520 		p_old->sub_supply(power_supply);
521 	}
522 
523 	leitung_t::set_net(p);
524 
525 	if(  p != NULL  ) {
526 		p->add_supply(power_supply);
527 	}
528 }
529 
set_power_supply(uint32 newsupply)530 void pumpe_t::set_power_supply(uint32 newsupply)
531 {
532 	// update power network
533 	powernet_t *const p = get_net();
534 	if(  p != NULL  ) {
535 		p->sub_supply(power_supply);
536 		p->add_supply(newsupply);
537 	}
538 
539 	power_supply = newsupply;
540 }
541 
get_power_consumption() const542 sint32 pumpe_t::get_power_consumption() const
543 {
544 	powernet_t const *const p = get_net();
545 	return p->get_normal_demand();
546 }
547 
rdwr(loadsave_t * file)548 void pumpe_t::rdwr(loadsave_t * file) {
549 	xml_tag_t d( file, "pumpe_t" );
550 
551 	leitung_t::rdwr(file);
552 
553 	// current power state
554 	if(  file->is_version_atleast(120, 4)  ) {
555 		file->rdwr_long(power_supply);
556 	}
557 }
558 
finish_rd()559 void pumpe_t::finish_rd()
560 {
561 	leitung_t::finish_rd();
562 	player_t::add_maintenance(get_owner(), -(sint32)welt->get_settings().cst_maintain_transformer, powerline_wt);
563 
564 	assert(get_net());
565 
566 	if(  fab==NULL  ) {
567 		if(welt->lookup(get_pos())->ist_karten_boden()) {
568 			// on surface, check around
569 			fab = leitung_t::suche_fab_4(get_pos().get_2d());
570 		}
571 		else {
572 			// underground, check directly above
573 			fab = fabrik_t::get_fab(get_pos().get_2d());
574 		}
575 		if(  fab  ) {
576 			// only add when factory there
577 			fab->set_transformer_connected(this);
578 		}
579 	}
580 
581 #ifdef MULTI_THREAD
582 	pthread_mutex_lock( &pumpe_list_mutex );
583 #endif
584 	pumpe_list.insert( this );
585 #ifdef MULTI_THREAD
586 	pthread_mutex_unlock( &pumpe_list_mutex );
587 #endif
588 #ifdef MULTI_THREAD
589 	pthread_mutex_lock( &calc_image_mutex );
590 #endif
591 	set_image(skinverwaltung_t::pumpe->get_image_id(0));
592 	is_crossing = false;
593 #ifdef MULTI_THREAD
594 	pthread_mutex_unlock( &calc_image_mutex );
595 #endif
596 }
597 
info(cbuffer_t & buf) const598 void pumpe_t::info(cbuffer_t & buf) const
599 {
600 	obj_t::info( buf );
601 
602 	buf.printf(translator::translate("Net ID: %p"), get_net());
603 	buf.printf("\n");
604 	buf.printf(translator::translate("Generation: %.0f MW"), (double)(power_supply >> POWER_TO_MW));
605 	buf.printf("\n");
606 	buf.printf(translator::translate("Usage: %.0f %%"), (double)((100 * get_net()->get_normal_demand()) >> powernet_t::FRACTION_PRECISION));
607 	buf.printf("\n"); // pad for consistent dialog size
608 }
609 
610 
611 /************************************ Distriubtion Transformer Code ********************************************/
612 
613 slist_tpl<senke_t *> senke_t::senke_list;
614 uint32 senke_t::payment_timer = 0;
615 
new_world()616 void senke_t::new_world()
617 {
618 	senke_list.clear();
619 	payment_timer = 0;
620 }
621 
static_rdwr(loadsave_t * file)622 void senke_t::static_rdwr(loadsave_t *file)
623 {
624 	if(  file->is_version_atleast(120, 4)  ) {
625 		file->rdwr_long(payment_timer);
626 	}
627 }
628 
step_all(uint32 delta_t)629 void senke_t::step_all(uint32 delta_t)
630 {
631 	// payment period (could be tied to game setting)
632 	const uint32 pay_period = PRODUCTION_DELTA_T * 10; // 10 seconds
633 
634 	// revenue payout timer
635 	payment_timer += delta_t;
636 	const bool payout = payment_timer >= pay_period;
637 	payment_timer %= pay_period;
638 
639 	// step all distribution transformers
640 	FOR(slist_tpl<senke_t*>, const s, senke_list) {
641 		s->step(delta_t);
642 		if (payout) {
643 			s->pay_revenue();
644 		}
645 	}
646 }
647 
senke_t(loadsave_t * file)648 senke_t::senke_t(loadsave_t *file) : leitung_t( koord3d::invalid, NULL )
649 {
650 	fab = NULL;
651 	delta_sum = 0;
652 	next_t = 0;
653 	power_demand = 0;
654 	energy_acc = 0;
655 
656 	rdwr( file );
657 
658 	welt->sync.add(this);
659 }
660 
661 
senke_t(koord3d pos,player_t * player)662 senke_t::senke_t(koord3d pos, player_t *player) : leitung_t(pos, player)
663 {
664 	fab = NULL;
665 	delta_sum = 0;
666 	next_t = 0;
667 	power_demand = 0;
668 	energy_acc = 0;
669 
670 	player_t::book_construction_costs(player, welt->get_settings().cst_transformer, get_pos().get_2d(), powerline_wt);
671 
672 	welt->sync.add(this);
673 }
674 
675 
~senke_t()676 senke_t::~senke_t()
677 {
678 	// one last final income
679 	pay_revenue();
680 
681 	welt->sync.remove( this );
682 	if(fab!=NULL) {
683 		fab->set_transformer_connected(NULL);
684 		fab = NULL;
685 	}
686 	if(  net != NULL  ) {
687 		net->sub_demand(power_demand);
688 	}
689 	senke_list.remove( this );
690 	player_t::add_maintenance(get_owner(), (sint32)welt->get_settings().cst_maintain_transformer, powerline_wt);
691 }
692 
step(uint32 delta_t)693 void senke_t::step(uint32 delta_t)
694 {
695 	if(  fab == NULL  ) {
696 		return;
697 	}
698 	else if(  delta_t == 0  ) {
699 		return;
700 	}
701 
702 	// energy metering logic
703 	energy_acc += ((uint64)power_demand * (uint64)get_net()->get_normal_supply() * (uint64)delta_t) / ((uint64)PRODUCTION_DELTA_T << powernet_t::FRACTION_PRECISION);
704 }
705 
pay_revenue()706 void senke_t::pay_revenue()
707 {
708 	// megajoules (megawatt seconds) per cent
709 	const uint64 mjpc = (1 << POWER_TO_MW) / 2; // should be tied to game setting
710 
711 	// calculate payment in cent
712 	const sint64 payment = (sint64)(energy_acc / mjpc);
713 
714 	// make payment
715 	if(  payment  >  0  ) {
716 		// enough has accumulated for a payment
717 		get_owner()->book_revenue( payment, get_pos().get_2d(), powerline_wt );
718 
719 		// remove payment from accumulator
720 		energy_acc %= mjpc;
721 	}
722 }
723 
set_net(powernet_t * p)724 void senke_t::set_net(powernet_t * p)
725 {
726 	powernet_t * p_old = get_net();
727 	if(  p_old != NULL  ) {
728 		p_old->sub_demand(power_demand);
729 	}
730 
731 	leitung_t::set_net(p);
732 
733 	if(  p != NULL  ) {
734 		p->add_demand(power_demand);
735 	}
736 }
737 
set_power_demand(uint32 newdemand)738 void senke_t::set_power_demand(uint32 newdemand)
739 {
740 	// update power network
741 	powernet_t *const p = get_net();
742 	if(  p != NULL  ) {
743 		p->sub_demand(power_demand);
744 		p->add_demand(newdemand);
745 	}
746 
747 	power_demand = newdemand;
748 }
749 
get_power_satisfaction() const750 sint32 senke_t::get_power_satisfaction() const
751 {
752 	powernet_t const *const p = get_net();
753 	return p->get_normal_supply();
754 }
755 
sync_step(uint32 delta_t)756 sync_result senke_t::sync_step(uint32 delta_t)
757 {
758 	if(fab==NULL) {
759 		return SYNC_DELETE;
760 	}
761 
762 	// advance timers
763 	delta_sum += delta_t;
764 	next_t += delta_t;
765 
766 	// change graphics at most 16 times a second
767 	if(  next_t > PRODUCTION_DELTA_T / 16  ) {
768 		// enforce timer periods
769 		delta_sum %= PRODUCTION_DELTA_T; // 1 second
770 		next_t %= PRODUCTION_DELTA_T / 16; // 1/16 seconds
771 
772 		// determine pwm period for image change
773 		uint32 pwm_period = 0;
774 		const sint32 satisfaction = get_net()->get_normal_supply();
775 		if(  satisfaction  >=  1 << powernet_t::FRACTION_PRECISION  ) {
776 			// always on
777 			pwm_period = PRODUCTION_DELTA_T;
778 		}
779 		else if(  satisfaction  >=  ((7 << powernet_t::FRACTION_PRECISION) / 8)  ) {
780 			// limit to at most 7/8 of a second
781 			pwm_period = 7 * PRODUCTION_DELTA_T / 8;
782 		}
783 		else if(  satisfaction  >  ((1 << powernet_t::FRACTION_PRECISION) / 8)  ) {
784 			// duty cycle based on power satisfaction
785 			pwm_period = (uint32)(((uint64)PRODUCTION_DELTA_T * (uint64)satisfaction) >> powernet_t::FRACTION_PRECISION);
786 		}
787 		else if(  satisfaction  >  0  ) {
788 			// limit to at least 1/8 of a second
789 			pwm_period = PRODUCTION_DELTA_T / 8;
790 		}
791 
792 		// determine image with PWM logic
793 		const uint16 work_offset = (delta_sum < pwm_period) ? 1 : 0;
794 
795 		// apply seasonal image offset
796 		uint16 winter_offset = 0;
797 		if(  skinverwaltung_t::senke->get_count() > 3  &&  (get_pos().z >= welt->get_snowline()  ||
798 			 welt->get_climate(get_pos().get_2d()) == arctic_climate)  ) {
799 			winter_offset = 2;
800 		}
801 
802 		// update displayed image
803 		image_id new_image = skinverwaltung_t::senke->get_image_id(work_offset + winter_offset);
804 		if(  image != new_image  ) {
805 			set_flag( obj_t::dirty );
806 			set_image( new_image );
807 		}
808 	}
809 	return SYNC_OK;
810 }
811 
rdwr(loadsave_t * file)812 void senke_t::rdwr(loadsave_t *file)
813 {
814 	xml_tag_t d( file, "senke_t" );
815 
816 	leitung_t::rdwr(file);
817 
818 	// current power state
819 	if(  file->is_version_atleast(120, 4)  ) {
820 		file->rdwr_longlong((sint64 &)energy_acc);
821 		file->rdwr_long(power_demand);
822 	}
823 }
824 
finish_rd()825 void senke_t::finish_rd()
826 {
827 	leitung_t::finish_rd();
828 	player_t::add_maintenance(get_owner(), -(sint32)welt->get_settings().cst_maintain_transformer, powerline_wt);
829 
830 	assert(get_net());
831 
832 	if(  fab==NULL  ) {
833 		if(welt->lookup(get_pos())->ist_karten_boden()) {
834 			// on surface, check around
835 			fab = leitung_t::suche_fab_4(get_pos().get_2d());
836 		}
837 		else {
838 			// underground, check directly above
839 			fab = fabrik_t::get_fab(get_pos().get_2d());
840 		}
841 		if(  fab  ) {
842 			fab->set_transformer_connected(this);
843 		}
844 	}
845 
846 #ifdef MULTI_THREAD
847 	pthread_mutex_lock( &senke_list_mutex );
848 #endif
849 	senke_list.insert( this );
850 #ifdef MULTI_THREAD
851 	pthread_mutex_unlock( &senke_list_mutex );
852 	pthread_mutex_lock( &calc_image_mutex );
853 #endif
854 	set_image(skinverwaltung_t::senke->get_image_id(0));
855 	is_crossing = false;
856 #ifdef MULTI_THREAD
857 	pthread_mutex_unlock( &calc_image_mutex );
858 #endif
859 }
860 
info(cbuffer_t & buf) const861 void senke_t::info(cbuffer_t & buf) const
862 {
863 	obj_t::info( buf );
864 
865 	buf.printf(translator::translate("Net ID: %p"), get_net());
866 	buf.printf("\n");
867 	buf.printf(translator::translate("Demand: %.0f MW"), (double)(power_demand >> POWER_TO_MW));
868 	buf.printf("\n");
869 	buf.printf(translator::translate("Supplied: %.0f %%"), (double)((100 * get_net()->get_normal_supply()) >> powernet_t::FRACTION_PRECISION));
870 	buf.printf("\n"); // pad for consistent dialog size
871 }
872