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 * Things on ways like catenary or something like this
8 *
9 * von prissi
10 */
11
12 #include <algorithm>
13
14 #include "../boden/grund.h"
15 #include "../simworld.h"
16 #include "../display/simimg.h"
17 #include "../simobj.h"
18 #include "../player/simplay.h"
19 #include "../simtool.h"
20
21 #include "../dataobj/loadsave.h"
22 #include "../dataobj/ribi.h"
23 #include "../dataobj/scenario.h"
24 #include "../dataobj/translator.h"
25 #include "../dataobj/environment.h"
26
27 #include "../descriptor/bridge_desc.h"
28 #include "../descriptor/tunnel_desc.h"
29 #include "../descriptor/way_obj_desc.h"
30
31 #include "../gui/tool_selector.h"
32
33 #include "../boden/grund.h"
34 #include "../boden/wege/weg.h"
35
36 #include "../tpl/stringhashtable_tpl.h"
37
38 #include "../utils/simstring.h"
39
40 #include "bruecke.h"
41 #include "tunnel.h"
42 #include "wayobj.h"
43
44 #ifdef MULTI_THREAD
45 #include "../utils/simthread.h"
46 static pthread_mutex_t wayobj_calc_image_mutex;
47 static recursive_mutex_maker_t wayobj_cim_maker(wayobj_calc_image_mutex);
48 #endif
49
50 // the descriptions ...
51 const way_obj_desc_t *wayobj_t::default_oberleitung=NULL;
52 stringhashtable_tpl<const way_obj_desc_t *> wayobj_t::table;
53
54
wayobj_t(loadsave_t * const file)55 wayobj_t::wayobj_t(loadsave_t* const file) : obj_no_info_t()
56 {
57 rdwr(file);
58 }
59
60
wayobj_t(koord3d const pos,player_t * const owner,ribi_t::ribi const d,way_obj_desc_t const * const b)61 wayobj_t::wayobj_t(koord3d const pos, player_t* const owner, ribi_t::ribi const d, way_obj_desc_t const* const b) : obj_no_info_t(pos)
62 {
63 desc = b;
64 dir = d;
65 set_owner(owner);
66 }
67
68
~wayobj_t()69 wayobj_t::~wayobj_t()
70 {
71 if(!desc) {
72 return;
73 }
74 if(get_owner()) {
75 player_t::add_maintenance(get_owner(), -desc->get_maintenance(), get_waytype());
76 }
77 if(desc->is_overhead_line()) {
78 grund_t *gr=welt->lookup(get_pos());
79 weg_t *weg=NULL;
80 if(gr) {
81 const waytype_t wt = (desc->get_wtyp()==tram_wt) ? track_wt : desc->get_wtyp();
82 weg = gr->get_weg(wt);
83 if(weg) {
84 // Weg wieder freigeben, wenn das Signal nicht mehr da ist.
85 weg->set_electrify(false);
86 // restore old speed limit
87 sint32 max_speed = weg->hat_gehweg() ? 50 : weg->get_desc()->get_topspeed();
88 if(gr->get_typ()==grund_t::tunnelboden) {
89 tunnel_t *t = gr->find<tunnel_t>(1);
90 if(t) {
91 max_speed = t->get_desc()->get_topspeed();
92 }
93 }
94 if(gr->get_typ()==grund_t::brueckenboden) {
95 bruecke_t *b = gr->find<bruecke_t>(1);
96 if(b) {
97 max_speed = b->get_desc()->get_topspeed();
98 }
99 }
100 weg->set_max_speed(max_speed);
101 }
102 else {
103 dbg->warning("wayobj_t::~wayobj_t()","ground was not a way!");
104 }
105 }
106 }
107
108 mark_image_dirty( get_front_image(), 0 );
109 mark_image_dirty( get_image(), 0 );
110 grund_t *gr = welt->lookup( get_pos() );
111 if( gr ) {
112 for( uint8 i = 0; i < 4; i++ ) {
113 // Remove ribis from adjacent wayobj.
114 if( ribi_t::nsew[i] & get_dir() ) {
115 grund_t *next_gr;
116 if( gr->get_neighbour( next_gr, desc->get_wtyp(), ribi_t::nsew[i] ) ) {
117 wayobj_t *wo2 = next_gr->get_wayobj( desc->get_wtyp() );
118 if( wo2 ) {
119 wo2->mark_image_dirty( wo2->get_front_image(), 0 );
120 wo2->mark_image_dirty( wo2->get_image(), 0 );
121 wo2->set_dir( wo2->get_dir() & ~ribi_t::backward(ribi_t::nsew[i]) );
122 wo2->mark_image_dirty( wo2->get_front_image(), 0 );
123 wo2->mark_image_dirty( wo2->get_image(), 0 );
124 wo2->set_flag(obj_t::dirty);
125 }
126 }
127 }
128 }
129 }
130 }
131
132
rdwr(loadsave_t * file)133 void wayobj_t::rdwr(loadsave_t *file)
134 {
135 xml_tag_t t( file, "wayobj_t" );
136 obj_t::rdwr(file);
137 if(file->is_version_atleast(89, 0)) {
138 uint8 ddir = dir;
139 file->rdwr_byte(ddir);
140 dir = ddir;
141 if(file->is_saving()) {
142 const char *s = desc->get_name();
143 file->rdwr_str(s);
144 }
145 else {
146 char bname[128];
147 file->rdwr_str(bname, lengthof(bname));
148
149 desc = wayobj_t::table.get(bname);
150 if(desc==NULL) {
151 desc = wayobj_t::table.get(translator::compatibility_name(bname));
152 if(desc==NULL) {
153 if(strstr(bname,"atenary") || strstr(bname,"electri") || strstr(bname,"power") ) {
154 desc = default_oberleitung;
155 }
156 }
157 if(desc==NULL) {
158 dbg->warning("wayobj_t::rwdr", "description %s for wayobj_t at %d,%d not found, will be removed!", bname, get_pos().x, get_pos().y );
159 welt->add_missing_paks( bname, karte_t::MISSING_WAYOBJ );
160 }
161 else {
162 dbg->warning("wayobj_t::rwdr", "wayobj %s at %d,%d replaced by %s", bname, get_pos().x, get_pos().y, desc->get_name() );
163 }
164 }
165 }
166 }
167 else {
168 desc = default_oberleitung;
169 dir = dir_unknown;
170 }
171 }
172
173
cleanup(player_t * player)174 void wayobj_t::cleanup(player_t *player)
175 {
176 if(desc) {
177 player_t::book_construction_costs(player, -desc->get_price(), get_pos().get_2d(), desc->get_wtyp());
178 }
179 }
180
181
182 // returns NULL, if removal is allowed
183 // players can remove public owned wayobjs
is_deletable(const player_t * player)184 const char *wayobj_t::is_deletable(const player_t *player)
185 {
186 if( get_player_nr()==welt->get_public_player()->get_player_nr() ) {
187 return NULL;
188 }
189 return obj_t::is_deletable(player);
190 }
191
192
finish_rd()193 void wayobj_t::finish_rd()
194 {
195 // (re)set dir
196 if(dir==dir_unknown) {
197 const waytype_t wt = (desc->get_wtyp()==tram_wt) ? track_wt : desc->get_wtyp();
198 weg_t *w=welt->lookup(get_pos())->get_weg(wt);
199 if(w) {
200 dir = w->get_ribi_unmasked();
201 }
202 else {
203 dir = ribi_t::all;
204 }
205 }
206
207 // electrify a way if we are a catenary
208 if(desc->is_overhead_line()) {
209 const waytype_t wt = (desc->get_wtyp()==tram_wt) ? track_wt : desc->get_wtyp();
210 weg_t *weg = welt->lookup(get_pos())->get_weg(wt);
211 if (wt == any_wt) {
212 weg_t *weg2 = welt->lookup(get_pos())->get_weg_nr(1);
213 if (weg2) {
214 weg2->set_electrify(true);
215 if(weg2->get_max_speed()>desc->get_topspeed()) {
216 weg2->set_max_speed(desc->get_topspeed());
217 }
218 }
219 }
220 if(weg) {
221 // Weg wieder freigeben, wenn das Signal nicht mehr da ist.
222 weg->set_electrify(true);
223 if(weg->get_max_speed()>desc->get_topspeed()) {
224 weg->set_max_speed(desc->get_topspeed());
225 }
226 }
227 else {
228 dbg->warning("wayobj_t::finish_rd()","ground was not a way!");
229 }
230 }
231
232 if(get_owner()) {
233 player_t::add_maintenance(get_owner(), desc->get_maintenance(), desc->get_wtyp());
234 }
235 }
236
237
rotate90()238 void wayobj_t::rotate90()
239 {
240 obj_t::rotate90();
241 dir = ribi_t::rotate90( dir);
242 hang = slope_t::rotate90( hang );
243 }
244
245
246 // helper function: gets the ribi on next tile
find_next_ribi(const grund_t * start,ribi_t::ribi const dir,const waytype_t wt) const247 ribi_t::ribi wayobj_t::find_next_ribi(const grund_t *start, ribi_t::ribi const dir, const waytype_t wt) const
248 {
249 grund_t *to;
250 ribi_t::ribi r1 = ribi_t::none;
251 if(start->get_neighbour(to,wt,dir)) {
252 const wayobj_t* wo = to->get_wayobj( wt );
253 if(wo) {
254 r1 = wo->get_dir();
255 }
256 }
257 return r1;
258 }
259
260
calc_image()261 void wayobj_t::calc_image()
262 {
263 #ifdef MULTI_THREAD
264 pthread_mutex_lock( &wayobj_calc_image_mutex );
265 #endif
266 grund_t *gr = welt->lookup(get_pos());
267 diagonal = false;
268 if(gr) {
269 const waytype_t wt = (desc->get_wtyp()==tram_wt) ? track_wt : desc->get_wtyp();
270 weg_t *w=gr->get_weg(wt);
271 if(!w) {
272 dbg->error("wayobj_t::calc_image()","without way at (%s)", get_pos().get_str() );
273 // well, we are not on a way anymore? => delete us
274 cleanup(get_owner());
275 delete this;
276 gr->set_flag(grund_t::dirty);
277 #ifdef MULTI_THREAD
278 pthread_mutex_unlock( &wayobj_calc_image_mutex );
279 #endif
280 return;
281 }
282
283 ribi_t::ribi sec_way_ribi_unmasked = 0;
284 if(wt == any_wt) {
285 if (weg_t *sec_w = gr->get_weg_nr(1)) {
286 sec_way_ribi_unmasked = sec_w->get_ribi_unmasked();
287 }
288 }
289
290 set_yoff( -gr->get_weg_yoff() );
291 dir &= (w->get_ribi_unmasked() | sec_way_ribi_unmasked);
292
293 // if there is a slope, we are finished, only four choices here (so far)
294 hang = gr->get_weg_hang();
295 if(hang!=slope_t::flat) {
296 #ifdef MULTI_THREAD
297 pthread_mutex_unlock( &wayobj_calc_image_mutex );
298 #endif
299 return;
300 }
301
302 // find out whether using diagonals or straight lines
303 if(ribi_t::is_bend(dir) && desc->has_diagonal_image()) {
304 ribi_t::ribi r1 = ribi_t::none, r2 = ribi_t::none;
305
306 // get the ribis of the ways that connect to us
307 // r1 will be 45 degree clockwise ribi (eg northeast->east), r2 will be anticlockwise ribi (eg northeast->north)
308 r1 = find_next_ribi( gr, ribi_t::rotate45(dir), wt );
309 r2 = find_next_ribi( gr, ribi_t::rotate45l(dir), wt );
310
311 // diagonal if r1 or r2 are our reverse and neither one is 90 degree rotation of us
312 diagonal = (r1 == ribi_t::backward(dir) || r2 == ribi_t::backward(dir)) && r1 != ribi_t::rotate90l(dir) && r2 != ribi_t::rotate90(dir);
313
314 if(diagonal) {
315 // with this, we avoid calling us endlessly
316 // HACK (originally by hajo?)
317 static int rekursion = 0;
318
319 if(rekursion == 0) {
320 grund_t *to;
321 rekursion++;
322 for(int r = 0; r < 4; r++) {
323 if(gr->get_neighbour(to, wt, ribi_t::nsew[r])) {
324 wayobj_t* wo = to->get_wayobj( wt );
325 if(wo) {
326 wo->calc_image();
327 }
328 }
329 }
330 rekursion--;
331 }
332
333 image_id after = desc->get_front_diagonal_image_id(dir);
334 image_id image = desc->get_back_diagonal_image_id(dir);
335 if(image==IMG_EMPTY && after==IMG_EMPTY) {
336 // no diagonals available
337 diagonal = false;
338 }
339 }
340 }
341 }
342 #ifdef MULTI_THREAD
343 pthread_mutex_unlock( &wayobj_calc_image_mutex );
344 #endif
345 }
346
347
348
349 /******************** static stuff from here **********************/
350
351
352 /* better use this constrcutor for new wayobj; it will extend a matching obj or make an new one
353 */
extend_wayobj(koord3d pos,player_t * owner,ribi_t::ribi dir,const way_obj_desc_t * desc,bool keep_existing_faster_way)354 void wayobj_t::extend_wayobj(koord3d pos, player_t *owner, ribi_t::ribi dir, const way_obj_desc_t *desc, bool keep_existing_faster_way)
355 {
356 grund_t *gr=welt->lookup(pos);
357 if(gr) {
358 wayobj_t *existing_wayobj = gr->get_wayobj( desc->get_wtyp() );
359 if( existing_wayobj ) {
360 if( ( existing_wayobj->get_desc()->get_topspeed() < desc->get_topspeed() || !keep_existing_faster_way) && player_t::check_owner(owner, existing_wayobj->get_owner())
361 && existing_wayobj->get_desc() != desc )
362 {
363 // replace slower by faster if desired
364 dir = dir | existing_wayobj->get_dir();
365 gr->set_flag(grund_t::dirty);
366 delete existing_wayobj;
367 }
368 else {
369 // extend this one instead
370 existing_wayobj->set_dir(dir|existing_wayobj->get_dir());
371 existing_wayobj->mark_image_dirty( existing_wayobj->get_front_image(), 0 );
372 existing_wayobj->mark_image_dirty( existing_wayobj->get_image(), 0 );
373 existing_wayobj->set_flag(obj_t::dirty);
374 return;
375 }
376 }
377
378 // nothing found => make a new one
379 wayobj_t *wo = new wayobj_t(pos,owner,dir,desc);
380 gr->obj_add(wo);
381 wo->finish_rd();
382 wo->calc_image();
383 wo->mark_image_dirty( wo->get_front_image(), 0 );
384 wo->set_flag(obj_t::dirty);
385 player_t::book_construction_costs( owner, -desc->get_price(), pos.get_2d(), desc->get_wtyp());
386
387 for( uint8 i = 0; i < 4; i++ ) {
388 // Extend wayobjects around the new one, that aren't already connected.
389 if( ribi_t::nsew[i] & ~wo->get_dir() ) {
390 grund_t *next_gr;
391 if( gr->get_neighbour( next_gr, desc->get_wtyp(), ribi_t::nsew[i] ) ) {
392 wayobj_t *wo2 = next_gr->get_wayobj( desc->get_wtyp() );
393 if( wo2 ) {
394 wo2->set_dir( wo2->get_dir() | ribi_t::backward(ribi_t::nsew[i]) );
395 wo2->mark_image_dirty( wo2->get_front_image(), 0 );
396 wo->set_dir( wo->get_dir() | ribi_t::nsew[i] );
397 wo->mark_image_dirty( wo->get_front_image(), 0 );
398 }
399 }
400 }
401 }
402 }
403 }
404
405
406
407 // to sort wayobj for always the same menu order
compare_wayobj_desc(const way_obj_desc_t * a,const way_obj_desc_t * b)408 static bool compare_wayobj_desc(const way_obj_desc_t* a, const way_obj_desc_t* b)
409 {
410 int diff = a->get_wtyp() - b->get_wtyp();
411 if (diff == 0) {
412 diff = a->get_topspeed() - b->get_topspeed();
413 }
414 if (diff == 0) {
415 /* Some speed: sort by name */
416 diff = strcmp(a->get_name(), b->get_name());
417 }
418 return diff < 0;
419 }
420
421
successfully_loaded()422 bool wayobj_t::successfully_loaded()
423 {
424 if(table.empty()) {
425 dbg->warning("wayobj_t::successfully_loaded()", "No obj found - may crash when loading catenary.");
426 }
427
428 way_obj_desc_t const* def = 0;
429 FOR(stringhashtable_tpl<way_obj_desc_t const*>, const& i, table) {
430 way_obj_desc_t const& b = *i.value;
431 if (!b.is_overhead_line()) continue;
432 if (b.get_wtyp() != track_wt) continue;
433 if (def && def->get_topspeed() >= b.get_topspeed()) continue;
434 def = &b;
435 }
436 default_oberleitung = def;
437
438 return true;
439 }
440
441
register_desc(way_obj_desc_t * desc)442 bool wayobj_t::register_desc(way_obj_desc_t *desc)
443 {
444 // avoid duplicates with same name
445 if( const way_obj_desc_t *old_desc = table.remove(desc->get_name()) ) {
446 dbg->doubled( "wayobj", desc->get_name() );
447 tool_t::general_tool.remove( old_desc->get_builder() );
448 delete old_desc->get_builder();
449 delete old_desc;
450 }
451
452 if( desc->get_cursor()->get_image_id(1)!=IMG_EMPTY ) {
453 // only add images for wayobjexts with cursor ...
454 tool_build_wayobj_t *tool = new tool_build_wayobj_t();
455 tool->set_icon( desc->get_cursor()->get_image_id(1) );
456 tool->cursor = desc->get_cursor()->get_image_id(0);
457 tool->set_default_param(desc->get_name());
458 tool_t::general_tool.append( tool );
459 desc->set_builder( tool );
460 }
461 else {
462 desc->set_builder( NULL );
463 }
464
465 table.put(desc->get_name(), desc);
466 DBG_DEBUG( "wayobj_t::register_desc()","%s", desc->get_name() );
467 return true;
468 }
469
470
471 /**
472 * Fill menu with icons of given wayobjects from the list
473 * @author Hj. Malthaner
474 */
fill_menu(tool_selector_t * tool_selector,waytype_t wtyp,sint16)475 void wayobj_t::fill_menu(tool_selector_t *tool_selector, waytype_t wtyp, sint16 /*sound_ok*/)
476 {
477 // check if scenario forbids this
478 if (!welt->get_scenario()->is_tool_allowed(welt->get_active_player(), TOOL_BUILD_WAYOBJ | GENERAL_TOOL, wtyp)) {
479 return;
480 }
481
482 const uint16 time=welt->get_timeline_year_month();
483
484 vector_tpl<const way_obj_desc_t *>matching;
485
486 FOR(stringhashtable_tpl<way_obj_desc_t const*>, const& i, table) {
487 way_obj_desc_t const* const desc = i.value;
488 if( desc->is_available(time) ) {
489
490 DBG_DEBUG("wayobj_t::fill_menu()", "try to add %s(%p)", desc->get_name(), desc);
491 if( desc->get_builder() && wtyp==desc->get_wtyp() ) {
492 // only add items with a cursor
493 matching.append(desc);
494 }
495 }
496 }
497 // sort the tools before adding to menu
498 std::sort(matching.begin(), matching.end(), compare_wayobj_desc);
499 FOR(vector_tpl<way_obj_desc_t const*>, const i, matching) {
500 tool_selector->add_tool_selector(i->get_builder());
501 }
502 }
503
504
get_overhead_line(waytype_t wt,uint16 time)505 const way_obj_desc_t *wayobj_t::get_overhead_line(waytype_t wt, uint16 time)
506 {
507 FOR(stringhashtable_tpl<way_obj_desc_t const*>, const& i, table) {
508 way_obj_desc_t const* const desc = i.value;
509 if( desc->is_available(time) && desc->get_wtyp()==wt && desc->is_overhead_line() ) {
510 return desc;
511 }
512 }
513 return NULL;
514 }
515
516
find_desc(const char * str)517 const way_obj_desc_t* wayobj_t::find_desc(const char *str)
518 {
519 return wayobj_t::table.get(str);
520 }
521