1 //       _________ __                 __
2 //      /   _____//  |_____________ _/  |______     ____  __ __  ______
3 //      \_____  \\   __\_  __ \__  \\   __\__  \   / ___\|  |  \/  ___/
4 //      /        \|  |  |  | \// __ \|  |  / __ \_/ /_/  >  |  /\___ |
5 //     /_______  /|__|  |__|  (____  /__| (____  /\___  /|____//____  >
6 //             \/                  \/          \//_____/            \/
7 //  ______________________                           ______________________
8 //                        T H E   W A R   B E G I N S
9 //         Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name action_move.cpp - The move action. */
12 //
13 //      (c) Copyright 1998-2006 by Lutz Sammer and Jimmy Salmon
14 //
15 //      This program is free software; you can redistribute it and/or modify
16 //      it under the terms of the GNU General Public License as published by
17 //      the Free Software Foundation; only version 2 of the License.
18 //
19 //      This program is distributed in the hope that it will be useful,
20 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
21 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 //      GNU General Public License for more details.
23 //
24 //      You should have received a copy of the GNU General Public License
25 //      along with this program; if not, write to the Free Software
26 //      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 //      02111-1307, USA.
28 //
29 
30 //@{
31 
32 /*----------------------------------------------------------------------------
33 --  Includes
34 ----------------------------------------------------------------------------*/
35 
36 #include "stratagus.h"
37 
38 #include "action/action_move.h"
39 
40 #include "ai.h"
41 #include "animation.h"
42 #include "interface.h"
43 #include "iolib.h"
44 #include "map.h"
45 #include "pathfinder.h"
46 #include "script.h"
47 #include "settings.h"
48 #include "sound.h"
49 #include "tileset.h"
50 #include "ui.h"
51 #include "unit.h"
52 #include "unit_find.h"
53 #include "unittype.h"
54 #include "video.h"
55 
56 /*----------------------------------------------------------------------------
57 --  Functions
58 ----------------------------------------------------------------------------*/
59 
NewActionMove(const Vec2i & pos)60 /* static */ COrder *COrder::NewActionMove(const Vec2i &pos)
61 {
62 	Assert(Map.Info.IsPointOnMap(pos));
63 
64 	COrder_Move *order = new COrder_Move;
65 
66 	order->goalPos = pos;
67 	return order;
68 }
69 
Save(CFile & file,const CUnit & unit) const70 /* virtual */ void COrder_Move::Save(CFile &file, const CUnit &unit) const
71 {
72 	file.printf("{\"action-move\",");
73 
74 	if (this->Finished) {
75 		file.printf(" \"finished\", ");
76 	}
77 	file.printf(" \"range\", %d,", this->Range);
78 	file.printf(" \"tile\", {%d, %d}", this->goalPos.x, this->goalPos.y);
79 
80 	file.printf("}");
81 }
82 
ParseSpecificData(lua_State * l,int & j,const char * value,const CUnit & unit)83 /* virtual */ bool COrder_Move::ParseSpecificData(lua_State *l, int &j, const char *value, const CUnit &unit)
84 {
85 	if (!strcmp(value, "range")) {
86 		++j;
87 		this->Range = LuaToNumber(l, -1, j + 1);
88 	} else if (!strcmp(value, "tile")) {
89 		++j;
90 		lua_rawgeti(l, -1, j + 1);
91 		CclGetPos(l, &this->goalPos.x , &this->goalPos.y);
92 		lua_pop(l, 1);
93 	} else {
94 		return false;
95 	}
96 	return true;
97 }
98 
IsValid() const99 /* virtual */ bool COrder_Move::IsValid() const
100 {
101 	return true;
102 }
103 
Show(const CViewport & vp,const PixelPos & lastScreenPos) const104 /* virtual */ PixelPos COrder_Move::Show(const CViewport &vp, const PixelPos &lastScreenPos) const
105 {
106 	const PixelPos targetPos = vp.TilePosToScreen_Center(this->goalPos);
107 
108 	Video.FillCircleClip(ColorGreen, lastScreenPos, 2);
109 	Video.DrawLineClip(ColorGreen, lastScreenPos, targetPos);
110 	Video.FillCircleClip(ColorGreen, targetPos, 3);
111 	return targetPos;
112 }
113 
UpdatePathFinderData(PathFinderInput & input)114 /* virtual */ void COrder_Move::UpdatePathFinderData(PathFinderInput &input)
115 {
116 	const Vec2i tileSize(0, 0);
117 	input.SetGoal(this->goalPos, tileSize);
118 
119 	int distance = this->Range;
120 	if (GameSettings.Inside) {
121 		CheckObstaclesBetweenTiles(input.GetUnitPos(), this->HasGoal() ? this->GetGoal()->tilePos : this->goalPos, MapFieldRocks | MapFieldForest, &distance);
122 	}
123 	input.SetMaxRange(distance);
124 	input.SetMinRange(0);
125 }
126 
127 /**
128 **  Unit moves! Generic function called from other actions.
129 **
130 **  @param unit  Pointer to unit.
131 **
132 **  @return      >0 remaining path length, 0 wait for path, -1
133 **               reached goal, -2 can't reach the goal.
134 */
DoActionMove(CUnit & unit)135 int DoActionMove(CUnit &unit)
136 {
137 	Vec2i posd; // movement in tile.
138 	int d;
139 
140 	if (!unit.Moving && (unit.Type->Animations->Move != unit.Anim.CurrAnim || !unit.Anim.Wait)) {
141 		Assert(!unit.Anim.Unbreakable);
142 
143 		// FIXME: So units flying up and down are not affected.
144 		unit.IX = 0;
145 		unit.IY = 0;
146 
147 		UnmarkUnitFieldFlags(unit);
148 		d = NextPathElement(unit, &posd.x, &posd.y);
149 		MarkUnitFieldFlags(unit);
150 		switch (d) {
151 			case PF_UNREACHABLE: // Can't reach, stop
152 				if (unit.Player->AiEnabled) {
153 					AiCanNotMove(unit);
154 				}
155 				unit.Moving = 0;
156 				return d;
157 			case PF_REACHED: // Reached goal, stop
158 				unit.Moving = 0;
159 				return d;
160 			case PF_WAIT: // No path, wait
161 				unit.Wait = 10;
162 
163 				unit.Moving = 0;
164 				return d;
165 			default: // On the way moving
166 				Assert(unit.CanMove());
167 				unit.Moving = 1;
168 				break;
169 		}
170 
171 		if (unit.Type->UnitType == UnitTypeNaval) { // Boat (un)docking?
172 			bool foundCoast = false;
173 
174 			for (int i = 0; i < unit.Type->TileWidth && !foundCoast; i++) {
175 				for (int j = 0; j < unit.Type->TileHeight && !foundCoast; j++) {
176 					const Vec2i offset(i, j);
177 					const CMapField &mf_next = *Map.Field(unit.tilePos + posd + offset);
178 					const CMapField &mf_cur = *Map.Field(unit.tilePos + offset);
179 
180 					if (mf_cur.CoastOnMap() || mf_next.CoastOnMap()) {
181 						foundCoast = true;
182 					}
183 				}
184 			}
185 			if (foundCoast) {
186 				// Should play, even if unit already speaking
187 				PlayUnitSound(unit, VoiceDocking, true);
188 			}
189 		}
190 		Vec2i pos = unit.tilePos + posd;
191 		unit.MoveToXY(pos);
192 
193 		// Remove unit from the current selection
194 		if (unit.Selected && !Map.Field(pos)->playerInfo.IsTeamVisible(*ThisPlayer)) {
195 			if (IsOnlySelected(unit)) { //  Remove building cursor
196 				CancelBuildingMode();
197 			}
198 			if (!ReplayRevealMap) {
199 				UnSelectUnit(unit);
200 				SelectionChanged();
201 			}
202 		}
203 
204 		unit.IX = -posd.x * PixelTileSize.x;
205 		unit.IY = -posd.y * PixelTileSize.y;
206 		unit.Frame = unit.Type->StillFrame;
207 		UnitHeadingFromDeltaXY(unit, posd);
208 	} else {
209 		posd.x = Heading2X[unit.Direction / NextDirection];
210 		posd.y = Heading2Y[unit.Direction / NextDirection];
211 		d = unit.pathFinderData->output.Length + 1;
212 	}
213 
214 	unit.pathFinderData->output.Cycles++;// reset have to be manualy controlled by caller.
215 	int move = UnitShowAnimationScaled(unit, unit.Type->Animations->Move, Map.Field(unit.Offset)->getCost());
216 
217 	unit.IX += posd.x * move;
218 	unit.IY += posd.y * move;
219 
220 	// Finished move animation, set Moving to 0 so we recalculate the path
221 	// next frame
222 	// FIXME: this is broken for subtile movement
223 	if (!unit.Anim.Unbreakable && !unit.IX && !unit.IY) {
224 		unit.Moving = 0;
225 	}
226 	return d;
227 }
228 
229 
Execute(CUnit & unit)230 /* virtual */ void COrder_Move::Execute(CUnit &unit)
231 {
232 	Assert(unit.CanMove());
233 
234 	if (unit.Wait) {
235 		if (!unit.Waiting) {
236 			unit.Waiting = 1;
237 			unit.WaitBackup = unit.Anim;
238 		}
239 		UnitShowAnimation(unit, unit.Type->Animations->Still);
240 		unit.Wait--;
241 		return;
242 	}
243 	if (unit.Waiting) {
244 		unit.Anim = unit.WaitBackup;
245 		unit.Waiting = 0;
246 	}
247 	// FIXME: (mr-russ) Make a reachable goal here with GoalReachable ...
248 
249 	switch (DoActionMove(unit)) { // reached end-point?
250 		case PF_UNREACHABLE:
251 			// Some tries to reach the goal
252 			this->Range++;
253 			break;
254 
255 		case PF_REACHED:
256 			this->Finished = true;
257 			break;
258 
259 		case PF_WAIT:
260 		{
261 			const CUnit *blocker = UnitOnMapTile(this->goalPos, unit.Type->UnitType);
262 			if (blocker) {
263 				const int distToBlocker = MapDistanceBetweenTypes(*(unit.Type), unit.tilePos, *(blocker->Type), blocker->tilePos);
264 				if (distToBlocker == 1 && (unit.IsEnemy(*blocker) || blocker->Moving == false)) {
265 					unit.Wait = 0;
266 					this->Finished = true;
267 				}
268 			}
269 		}	break;
270 		default:
271 			break;
272 	}
273 }
274 
275 /**
276 **  Get goal position
277 */
GetGoalPos() const278 /* virtual */ const Vec2i COrder_Move::GetGoalPos() const
279 {
280 	const Vec2i invalidPos(-1, -1);
281 	if (goalPos != invalidPos) {
282 		return goalPos;
283 	}
284 	if (this->HasGoal()) {
285 		return this->GetGoal()->tilePos;
286 	}
287 	return invalidPos;
288 }
289 
290 //@}
291