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