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