1 /*
2  * Copyright (c) 1997 - 2001 Hj. Malthaner
3  *
4  * This file is part of the Simutrans project under the artistic license.
5  * (see license.txt)
6  */
7 
8 /*
9  * Basic class of all visible things
10  *
11  * Hj. Maltahner
12  */
13 
14 #include <string.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 
18 #include "simdebug.h"
19 #include "display/simimg.h"
20 #include "simcolor.h"
21 #include "display/simgraph.h"
22 #include "display/viewport.h"
23 #include "gui/simwin.h"
24 #include "player/simplay.h"
25 #include "simobj.h"
26 #include "simworld.h"
27 #include "obj/baum.h"
28 #include "vehicle/simvehicle.h"
29 #include "dataobj/translator.h"
30 #include "dataobj/loadsave.h"
31 #include "boden/grund.h"
32 #include "gui/obj_info.h"
33 #include "utils/cbuffer_t.h"
34 #include "utils/simstring.h"
35 
36 
37 /**
38  * Pointer to the world of this thing. Static to conserve space.
39  * Change to instance variable once more than one world is available.
40  * @author Hj. Malthaner
41  */
42 karte_ptr_t obj_t::welt;
43 
44 bool obj_t::show_owner = false;
45 
46 
init()47 void obj_t::init()
48 {
49 	pos = koord3d::invalid;
50 
51 	xoff = 0;
52 	yoff = 0;
53 
54 	owner_n = PLAYER_UNOWNED;
55 
56 	flags = no_flags;
57 	set_flag(dirty);
58 }
59 
60 
obj_t()61 obj_t::obj_t()
62 {
63 	init();
64 }
65 
obj_t(koord3d pos)66 obj_t::obj_t(koord3d pos)
67 {
68 	init();
69 	this->pos = pos;
70 }
71 
72 
73 // removes an object and tries to delete it also from the corresponding objlist
~obj_t()74 obj_t::~obj_t()
75 {
76 	destroy_win((ptrdiff_t)this);
77 
78 	if(flags&not_on_map  ||  !welt->is_within_limits(pos.get_2d())) {
79 		return;
80 	}
81 
82 	// find object on the map and remove it
83 	grund_t *gr = welt->lookup(pos);
84 	if(!gr  ||  !gr->obj_remove(this)) {
85 		// not found? => try harder at all map locations
86 		dbg->warning("obj_t::~obj_t()","couldn't remove %p from %d,%d,%d",this, pos.x , pos.y, pos.z);
87 
88 		// first: try different height ...
89 		gr = welt->access(pos.get_2d())->get_boden_von_obj(this);
90 		if(gr  &&  gr->obj_remove(this)) {
91 			dbg->warning("obj_t::~obj_t()",
92 				"removed %p from %d,%d,%d, but it should have been on %d,%d,%d",
93 				this,
94 				gr->get_pos().x, gr->get_pos().y, gr->get_pos().z,
95 				pos.x, pos.y, pos.z);
96 			return;
97 		}
98 
99 		// then search entire map
100 		koord k;
101 		for(k.y=0; k.y<welt->get_size().y; k.y++) {
102 			for(k.x=0; k.x<welt->get_size().x; k.x++) {
103 				grund_t *gr = welt->access(k)->get_boden_von_obj(this);
104 				if (gr && gr->obj_remove(this)) {
105 					dbg->warning("obj_t::~obj_t()",
106 						"removed %p from %d,%d,%d, but it should have been on %d,%d,%d",
107 						this,
108 						gr->get_pos().x, gr->get_pos().y, gr->get_pos().z,
109 						pos.x, pos.y, pos.z);
110 					return;
111 				}
112 			}
113 		}
114 	}
115 }
116 
117 
118 /**
119  * sets owner of object
120  */
set_owner(player_t * player)121 void obj_t::set_owner(player_t *player)
122 {
123 	int i = welt->sp2num(player);
124 	assert(i>=0);
125 	owner_n = (uint8)i;
126 }
127 
128 
get_owner() const129 player_t *obj_t::get_owner() const
130 {
131 	return welt->get_player(owner_n);
132 }
133 
134 
135 /* the only general info we can give is the name
136  * we want to format it nicely,
137  * with two linebreaks at the end => thus the little extra effort
138  */
info(cbuffer_t & buf) const139 void obj_t::info(cbuffer_t & buf) const
140 {
141 	char              translation[256];
142 	char const* const owner =
143 		owner_n == 1              ? translator::translate("Eigenbesitz\n")   :
144 		owner_n == PLAYER_UNOWNED ? translator::translate("Kein Besitzer\n") :
145 		get_owner()->get_name();
146 	tstrncpy(translation, owner, lengthof(translation));
147 	// remove trailing linebreaks etc.
148 	rtrim(translation);
149 	buf.append( translation );
150 	// only append linebreaks if not empty
151 	if(  buf.len()>0  ) {
152 		buf.append( "\n\n" );
153 	}
154 }
155 
156 
show_info()157 void obj_t::show_info()
158 {
159 	create_win( new obj_infowin_t(this), w_info, (ptrdiff_t)this);
160 }
161 
has_managed_lifecycle() const162 bool obj_t::has_managed_lifecycle() const {
163 	return false;
164 }
165 
166 // returns NULL, if removal is allowed
is_deletable(const player_t * player)167 const char *obj_t::is_deletable(const player_t *player)
168 {
169 	if(owner_n==PLAYER_UNOWNED  ||  welt->get_player(owner_n) == player  ||  welt->get_public_player() == player) {
170 		return NULL;
171 	}
172 	else {
173 		return "Der Besitzer erlaubt das Entfernen nicht";
174 	}
175 }
176 
177 
rdwr(loadsave_t * file)178 void obj_t::rdwr(loadsave_t *file)
179 {
180 	xml_tag_t d( file, "obj_t" );
181 	if(  file->is_version_less(101, 0)  ) {
182 		pos.rdwr( file );
183 	}
184 
185 	sint8 byte = (sint8)(((sint16)16*(sint16)xoff)/OBJECT_OFFSET_STEPS);
186 	file->rdwr_byte(byte);
187 	xoff = (sint8)(((sint16)byte*OBJECT_OFFSET_STEPS)/16);
188 	byte = (sint8)(((sint16)16*(sint16)yoff)/OBJECT_OFFSET_STEPS);
189 	file->rdwr_byte(byte);
190 	yoff = (sint8)(((sint16)byte*OBJECT_OFFSET_STEPS)/16);
191 	byte = owner_n;
192 	file->rdwr_byte(byte);
193 	owner_n = byte;
194 }
195 
196 
197 /**
198  * draw the object
199  * the dirty-flag is reset from objlist_t::display_obj_fg, or objlist_t::display_overlay when multithreaded
200  */
display(int xpos,int ypos CLIP_NUM_DEF) const201 void obj_t::display(int xpos, int ypos  CLIP_NUM_DEF) const
202 {
203 	image_id image = get_image();
204 	image_id const outline_image = get_outline_image();
205 	if(  image!=IMG_EMPTY  ||  outline_image!=IMG_EMPTY  ) {
206 		const int raster_width = get_current_tile_raster_width();
207 		const bool is_dirty = get_flag(obj_t::dirty);
208 
209 		if (vehicle_base_t const* const v = obj_cast<vehicle_base_t>(this)) {
210 			// vehicles need finer steps to appear smoother
211 			v->get_screen_offset( xpos, ypos, raster_width );
212 		}
213 		xpos += tile_raster_scale_x(get_xoff(), raster_width);
214 		ypos += tile_raster_scale_y(get_yoff(), raster_width);
215 
216 		const int start_ypos = ypos;
217 		for(  int j=0;  image!=IMG_EMPTY;  ) {
218 
219 			if(  owner_n != PLAYER_UNOWNED  ) {
220 				if(  obj_t::show_owner  ) {
221 					display_blend( image, xpos, ypos, owner_n, color_idx_to_rgb(welt->get_player(owner_n)->get_player_color1()+2) | OUTLINE_FLAG | TRANSPARENT75_FLAG, 0, is_dirty  CLIP_NUM_PAR);
222 				}
223 				else {
224 					display_color( image, xpos, ypos, owner_n, true, is_dirty  CLIP_NUM_PAR);
225 				}
226 			}
227 			else {
228 				display_normal( image, xpos, ypos, 0, true, is_dirty  CLIP_NUM_PAR);
229 			}
230 			// this obj has another image on top (e.g. skyscraper)
231 			ypos -= raster_width;
232 			image = get_image(++j);
233 		}
234 
235 		if(  outline_image != IMG_EMPTY  ) {
236 			// transparency?
237 			const FLAGGED_PIXVAL transparent = get_outline_colour();
238 			if(  TRANSPARENT_FLAGS&transparent  ) {
239 				// only transparent outline
240 				display_blend( get_outline_image(), xpos, start_ypos, owner_n, transparent, 0, is_dirty  CLIP_NUM_PAR);
241 			}
242 			else if(  obj_t::get_flag( highlight )  ) {
243 				// highlight this tile
244 				display_blend( get_image(), xpos, start_ypos, owner_n, color_idx_to_rgb(COL_RED) | OUTLINE_FLAG | TRANSPARENT75_FLAG, 0, is_dirty  CLIP_NUM_PAR);
245 			}
246 		}
247 		else if(  obj_t::get_flag( highlight )  ) {
248 			// highlight this tile
249 			display_blend( get_image(), xpos, start_ypos, owner_n, color_idx_to_rgb(COL_RED) | OUTLINE_FLAG | TRANSPARENT75_FLAG, 0, is_dirty  CLIP_NUM_PAR);
250 		}
251 	}
252 }
253 
254 
255 // called during map rotation
rotate90()256 void obj_t::rotate90()
257 {
258 	// most basic: rotate coordinate
259 	pos.rotate90( welt->get_size().y-1 );
260 	if(xoff!=0) {
261 		sint8 new_dx = -2*yoff;
262 		yoff = xoff/2;
263 		xoff = new_dx;
264 	}
265 }
266 
267 
268 #ifdef MULTI_THREAD
display_after(int xpos,int ypos,const sint8 clip_num) const269 void obj_t::display_after(int xpos, int ypos, const sint8 clip_num) const
270 #else
271 void obj_t::display_after(int xpos, int ypos, bool) const
272 #endif
273 {
274 	image_id image = get_front_image();
275 	if(  image != IMG_EMPTY  ) {
276 		const int raster_width = get_current_tile_raster_width();
277 		const bool is_dirty = get_flag( obj_t::dirty );
278 
279 		xpos += tile_raster_scale_x( get_xoff(), raster_width );
280 		ypos += tile_raster_scale_y( get_yoff(), raster_width );
281 
282 		if(  owner_n != PLAYER_UNOWNED  ) {
283 			if(  obj_t::show_owner  ) {
284 				display_blend( image, xpos, ypos, owner_n, color_idx_to_rgb(welt->get_player(owner_n)->get_player_color1()+2) | OUTLINE_FLAG | TRANSPARENT75_FLAG, 0, is_dirty  CLIP_NUM_PAR);
285 			}
286 			else if(  obj_t::get_flag( highlight )  ) {
287 				// highlight this tile
288 				display_blend( image, xpos, ypos, owner_n, color_idx_to_rgb(COL_RED) | OUTLINE_FLAG | TRANSPARENT75_FLAG, 0, is_dirty  CLIP_NUM_PAR);
289 			}
290 			else {
291 				display_color( image, xpos, ypos, owner_n, true, is_dirty  CLIP_NUM_PAR);
292 			}
293 		}
294 		else if(  obj_t::get_flag( highlight )  ) {
295 			// highlight this tile
296 			display_blend( image, xpos, ypos, owner_n, color_idx_to_rgb(COL_RED) | OUTLINE_FLAG | TRANSPARENT75_FLAG, 0, is_dirty  CLIP_NUM_PAR);
297 		}
298 		else {
299 			display_normal( image, xpos, ypos, 0, true, is_dirty  CLIP_NUM_PAR);
300 		}
301 	}
302 }
303 
304 
305 /*
306  * when a vehicle moves or a cloud moves, it needs to mark the old spot as dirty (to copy to screen)
307  * sometimes they have an extra offset, this is the yoff parameter
308 * @author prissi
309  */
mark_image_dirty(image_id image,sint16 yoff) const310 void obj_t::mark_image_dirty(image_id image, sint16 yoff) const
311 {
312 	if(  image != IMG_EMPTY  ) {
313 		const sint16 rasterweite = get_tile_raster_width();
314 		int xpos=0, ypos=0;
315 		if(  is_moving()  ) {
316 			vehicle_base_t const* const v = obj_cast<vehicle_base_t>(this);
317 			// vehicles need finer steps to appear smoother
318 			v->get_screen_offset( xpos, ypos, get_tile_raster_width() );
319 		}
320 
321 		viewport_t *vp = welt->get_viewport();
322 		scr_coord scr_pos = vp->get_screen_coord(get_pos(), koord(get_xoff(), get_yoff()));
323 		// xpos, ypos, yoff are already in pixel units, no scaling needed
324 
325 		// mark the region after the image as dirty
326 		display_mark_img_dirty( image, scr_pos.x + xpos, scr_pos.y + ypos + yoff);
327 
328 		// too close to border => set dirty to be sure (smoke, skyscrapers, birds, or the like)
329 		KOORD_VAL xbild, ybild, wbild, hbild;
330 		display_get_image_offset( image, &xbild, &ybild, &wbild, &hbild );
331 		const sint16 distance_to_border = 3 - (yoff+get_yoff()+ybild)/(rasterweite/4);
332 		if(  pos.x <= distance_to_border  ||  pos.y <= distance_to_border  ) {
333 			// but only if the image is actually visible ...
334 			if(   scr_pos.x+xbild+wbild >= 0  &&  xpos <= display_get_width()  &&   scr_pos.y+ybild+hbild >= 0  &&  ypos+ybild < display_get_height()  ) {
335 				welt->set_background_dirty();
336 			}
337 		}
338 	}
339 }
340