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