1 /**
2 	ConstructionPreviewer
3 
4 
5 	@author Clonkonaut
6 */
7 
8 static const CONSTRUCTION_STICK_Left = 1;
9 static const CONSTRUCTION_STICK_Right = 2;
10 static const CONSTRUCTION_STICK_Bottom = 4;
11 static const CONSTRUCTION_STICK_Top = 8;
12 
13 local extra_overlay, dimension_x, dimension_y, clonk, structure, direction, stick_to, blocked;
14 local GFX_StructureOverlay = 1;
15 local GFX_CombineIconOverlay = 2;
16 local GFX_PreviewerPictureOverlay = 2;
17 
GetFlipDescription()18 public func GetFlipDescription() { return "$TxtFlipDesc$"; }
19 
Initialize()20 func Initialize()
21 {
22 	SetProperty("Visibility", VIS_Owner);
23 }
24 
Set(id to_construct,object constructing_clonk)25 func Set(id to_construct, object constructing_clonk)
26 {
27 	SetGraphics(nil, to_construct, GFX_StructureOverlay, GFXOV_MODE_Base);
28 	SetGraphics(nil, to_construct, GFX_PreviewerPictureOverlay, GFXOV_MODE_Picture, nil, GFX_BLIT_Wireframe);
29 	// Buildings may add something to the preview and can do so on GFX_PreviewerPictureOverlay
30 	// This is used by the elevator to preview the case
31 	// The definition should return true
32 	extra_overlay = to_construct->~ConstructionPreview(this, GFX_PreviewerPictureOverlay, direction);
33 	dimension_x = to_construct->GetDefWidth();
34 	dimension_y = to_construct->GetDefHeight();
35 	clonk = constructing_clonk;
36 	structure = to_construct;
37 	direction = DIR_Left;
38 	blocked = true;
39 	// Allow the definition to draw an alternative preview.
40 	to_construct->~AlternativeConstructionPreview(this, direction);
41 	AdjustPreview(structure->~IsBelowSurfaceConstruction());
42 	return;
43 }
44 
45 // Positions the preview according to the landscape, coloring it green, yellow or red
AdjustPreview(bool below_surface,bool look_up,bool no_call)46 public func AdjustPreview(bool below_surface, bool look_up, bool no_call)
47 {
48 	var half_y = dimension_y / 2;
49 	blocked = false;
50 	// Do only if not sticking to another object
51 	if (!stick_to)
52 	{
53 		// Place on material
54 		var search_dir = 1;
55 		if (look_up) search_dir = -1;
56 		var x = 0, y = 0;
57 		while (!(!GBackSolid(x,y + half_y) && GBackSolid(x,y + half_y + 1)))
58 		{
59 			y += search_dir;
60 			if (Abs(y) > half_y)
61 			{
62 				blocked = true;
63 				break;
64 			}
65 		}
66 
67 		if (blocked && !no_call)
68 			return AdjustPreview(below_surface, !look_up, true);
69 		if (blocked)
70 		{
71 			if (extra_overlay) SetClrModulation(RGBa(255,50,50, 100), GFX_PreviewerPictureOverlay);
72 			return SetClrModulation(RGBa(255,50,50, 100), GFX_StructureOverlay);
73 		}
74 		// Position depends on whether the object should below surface.
75 		if (!below_surface)
76 			SetPosition(GetX(), GetY() + y);
77 		else
78 			SetPosition(GetX(), GetY() + y + dimension_y + 1);
79 	}
80 	// Check for construction site.
81 	if (!below_surface && !CheckConstructionSite(structure, 0, half_y))
82 		blocked = true;
83 	// intersection-check with all other construction sites... bah
84 	for (var other_site in FindObjects(Find_ID(ConstructionSite)))
85 	{
86 		if (!(other_site->GetLeftEdge() > GetX()+dimension_x/2 || other_site->GetRightEdge() < GetX()-dimension_x/2 || other_site->GetTopEdge() > GetY()+half_y || other_site->GetBottomEdge() < GetY()-half_y))
87 			blocked = true;
88 	}
89 
90 	if(!blocked)
91 	{
92 		if (!stick_to)
93 		{
94 			if (extra_overlay) SetClrModulation(RGBa(50,255,50, 100), GFX_PreviewerPictureOverlay);
95 			SetClrModulation(RGBa(50,255,50, 100), GFX_StructureOverlay);
96 		}
97 		else
98 		{
99 			if (extra_overlay) SetClrModulation(RGBa(255,255,50, 200), GFX_PreviewerPictureOverlay);
100 			SetClrModulation(RGBa(255,255,50, 200), GFX_StructureOverlay);
101 		}
102 	}
103 	else
104 	{
105 		if (extra_overlay) SetClrModulation(RGBa(255,50,50, 100), GFX_PreviewerPictureOverlay);
106 		SetClrModulation(RGBa(255,50,50, 100), GFX_StructureOverlay);
107 	}
108 }
109 
110 // Positions the preview according to the mouse cursor, calls AdjustPreview afterwards
111 // x and y are refined mouse coordinates so always centered at the clonk
Reposition(int x,int y)112 func Reposition(int x, int y)
113 {
114 	var clonk_width = clonk->GetObjWidth();
115 	var clonk_height = clonk->GetObjHeight();
116 	x = BoundBy(x, -dimension_x - clonk_width/2, dimension_x + clonk_width/2);
117 	y = BoundBy(y, -dimension_y - clonk_height/2, dimension_y + clonk_height/2);
118 	// Try to combine the structure with other structures.
119 	var found = false;
120 	if (structure->~ConstructionCombineWith())
121 	{
122 		// There is no use in doing all the other checks if no sticking direction is defined at all
123 		// That's just wrong use of ConstructionCombineWith
124 		if (structure->~ConstructionCombineDirection())
125 		{
126 			//var find_rect = Find_InRect(AbsX(clonk->GetX() + x - dimension_x/2 - 10), AbsY(clonk->GetY() + y - dimension_y/2 - 10), dimension_x + 20, dimension_y + 20);
127 			//if ((stick_dir & CONSTRUCTION_STICK_Bottom))
128 
129 			var find_rect = Find_AtPoint(clonk->GetX() - GetX() + x, clonk->GetY() - GetY() + y);
130 
131 			var other = FindObject(Find_Func(structure->ConstructionCombineWith(), this),
132 										  find_rect,
133 										  Find_OCF(OCF_Fullcon),
134 										  Find_Layer(clonk->GetObjectLayer()),
135 										  Find_Allied(clonk->GetOwner()),
136 										  Find_NoContainer());
137 
138 			if (other)
139 			{
140 				var stick_dir = structure->ConstructionCombineDirection(other);
141 				var other_width = other->GetObjWidth();
142 				var other_height = other->GetObjHeight();
143 				// Determine the position from the other object's center currently hovered
144 				var other_offset_x = clonk->GetX() + x - other->GetX();
145 				var other_offset_y = clonk->GetY() + y - other->GetY();
146 
147 				// The tricky part is now to determine which of the four possible directions should be used
148 				// The shape of the 'other' is divided like this (* is center):
149 				//  __________________________
150 				// |       |   Top   |       |
151 				// | Left  |----*----| Right |
152 				// |_______|_Bottom__|_______|
153 				//
154 				// Whichever part is howered on is checked first for stick direction
155 
156 				// Hopefully, in the end this contains a single sticking direction.
157 				var single_stick_to = 0;
158 
159 				// Left
160 				if (other_offset_x < other_width / -6)
161 				{
162 					single_stick_to = GetStickingDirection(stick_dir, CONSTRUCTION_STICK_Left, CONSTRUCTION_STICK_Bottom, CONSTRUCTION_STICK_Top, CONSTRUCTION_STICK_Right, other_offset_y, 0);
163 				}
164 				// Right
165 				else if (other_offset_x > other_width / 6)
166 				{
167 					single_stick_to = GetStickingDirection(stick_dir, CONSTRUCTION_STICK_Right, CONSTRUCTION_STICK_Bottom, CONSTRUCTION_STICK_Top, CONSTRUCTION_STICK_Left, other_offset_y, 0);
168 				}
169 				// Bottom
170 				else if (other_offset_y >= 0)
171 				{
172 					single_stick_to = GetStickingDirection(stick_dir, CONSTRUCTION_STICK_Bottom, CONSTRUCTION_STICK_Right, CONSTRUCTION_STICK_Left, CONSTRUCTION_STICK_Top, other_offset_x, 0);
173 				}
174 				// Top
175 				else if (other_offset_y < 0)
176 				{
177 					single_stick_to = GetStickingDirection(stick_dir, CONSTRUCTION_STICK_Top, CONSTRUCTION_STICK_Right, CONSTRUCTION_STICK_Left, CONSTRUCTION_STICK_Bottom, other_offset_x, 0);
178 				}
179 
180 				// If no direction is found, something went wrong.
181 				// Probably ConstructionCombineDirection() returned garbage.
182 				if (single_stick_to)
183 				{
184 					if (single_stick_to == CONSTRUCTION_STICK_Left)
185 					{
186 						x = other->GetX() - other_width/2 - dimension_x/2;
187 						y = other->GetY();
188 					}
189 					if (single_stick_to == CONSTRUCTION_STICK_Right)
190 					{
191 						x = other->GetX() + other_width/2 + dimension_x/2;
192 						y = other->GetY();
193 					}
194 					if (single_stick_to == CONSTRUCTION_STICK_Bottom)
195 					{
196 						x = other->GetX();
197 						y = other->GetY() + other_height/2 + dimension_y/2;
198 					}
199 					if (single_stick_to == CONSTRUCTION_STICK_Top)
200 					{
201 						x = other->GetX();
202 						y = other->GetY() - other_height/2 - dimension_y/2;
203 					}
204 					// Add an additional offset if needed, for example a basement can be place
205 					// only under a part of the structure.
206 					var stick_offset = structure->~ConstructionCombineOffset(other, single_stick_to);
207 					if (stick_offset)
208 					{
209 						x += stick_offset[0];
210 						y += stick_offset[1];
211 					}
212 					// Save the other building for use in AdjustPreview and for color changing
213 					stick_to = other;
214 					// Found another building and a way to stick to it!
215 					found = true;
216 				}
217 			}
218 		}
219 	}
220 
221 	if (!found)
222 	{
223 		// Narrow the distance a construction site can be built around the clonk
224 		x = BoundBy(x, -dimension_x/2 - clonk_width/2, dimension_x/2 + clonk_width/2);
225 		y = BoundBy(y, -dimension_y/2 - clonk_height/2, dimension_y/2 + clonk_height/2);
226 		x = clonk->GetX() + x;
227 		y = clonk->GetY() + y;
228 	}
229 
230 	if (!found && stick_to)
231 	{
232 		stick_to = nil;
233 		SetGraphics(nil, nil, GFX_CombineIconOverlay);
234 	}
235 	else if (stick_to)
236 	{
237 		SetGraphics(nil, ConstructionPreviewer_IconCombine, GFX_CombineIconOverlay, GFXOV_MODE_Base);
238 		var x_dir = 1, y_dir = 1;
239 		if (stick_to->GetX() < GetX()) x_dir = -1;
240 		if (stick_to->GetY() < GetY()) y_dir = -1;
241 		if (single_stick_to == CONSTRUCTION_STICK_Bottom || single_stick_to == CONSTRUCTION_STICK_Top)
242 			x_dir = 0;
243 		if (single_stick_to == CONSTRUCTION_STICK_Left || single_stick_to == CONSTRUCTION_STICK_Right)
244 			y_dir = 0;
245 		SetObjDrawTransform(1000, 0, dimension_x/2 * 1000 * x_dir, 0, 1000, dimension_y/2 * 1000 * y_dir, GFX_CombineIconOverlay);
246 	}
247 	// Update the extra overlay possibly added to the preview.
248 	extra_overlay = structure->~ConstructionPreview(this, GFX_PreviewerPictureOverlay, direction);
249 	// Update alternative preview in the definition to be placed.
250 	structure->~AlternativeConstructionPreview(this, direction, stick_to);
251 	SetPosition(x, y);
252 	AdjustPreview(structure->~IsBelowSurfaceConstruction());
253 }
254 
255 // Helper function to return a definite sticking direction.
256 // Used whenever the cursor hovers the 'left' or 'right' part of the other building.
257 // See Reposition() to see an example of the four parts.
GetStickingDirection(int stick_dir,int primary_dir,int secondary_dir,int tertiary_dir,int fourth_dir,int cursor_coord,int other_coord)258 func GetStickingDirection(int stick_dir, int primary_dir, int secondary_dir, int tertiary_dir, int fourth_dir, int cursor_coord, int other_coord)
259 {
260 	// If the primary direction is in stick_dir, we're done
261 	if (stick_dir & primary_dir)
262 	{
263 		return primary_dir;
264 	}
265 	// Afterwards check second / third directions
266 	else if (stick_dir & secondary_dir || stick_dir & tertiary_dir)
267 	{
268 		// If one of those isn't in stick_dir, no coordinate checking is necessary
269 		if (!(stick_dir & tertiary_dir))
270 		{
271 			return secondary_dir;
272 		}
273 		else if (!(stick_dir & secondary_dir))
274 		{
275 			return tertiary_dir;
276 		}
277 		else
278 		{
279 			// Coordinates have to be checked
280 			// secondary always is one pixel better than tertiary
281 			if (cursor_coord >= other_coord)
282 				return secondary_dir;
283 			else
284 				return tertiary_dir;
285 		}
286 	}
287 	// Fourth direction is returned last but no other directions takes the point
288 	else if (stick_dir & fourth_dir)
289 	{
290 		return fourth_dir;
291 	}
292 	// If this happens, something is wrong
293 	return 0;
294 }
295 
296 // Flips the preview horizontally
Flip()297 func Flip()
298 {
299 	// Flip not allowed?
300 	if (structure->~NoConstructionFlip()) return;
301 
302 	if (direction == DIR_Left)
303 	{
304 		direction = DIR_Right;
305 		SetObjDrawTransform(-1000,0,0, 0,1000,0, GFX_StructureOverlay);
306 	} else {
307 		direction = DIR_Left;
308 		SetObjDrawTransform(1000,0,0, 0,1000,0, GFX_StructureOverlay);
309 	}
310 	if (extra_overlay)
311 			structure->~ConstructionPreview(this, GFX_PreviewerPictureOverlay, direction);
312 }
313 
314 // UI not saved.
SaveScenarioObject()315 func SaveScenarioObject() { return false; }
316 
317 local Plane = 210;