OpenTTD
ship_cmd.cpp
Go to the documentation of this file.
1 /* $Id: ship_cmd.cpp 27864 2017-05-03 20:13:05Z frosch $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8  */
9 
12 #include "stdafx.h"
13 #include "ship.h"
14 #include "landscape.h"
15 #include "timetable.h"
16 #include "news_func.h"
17 #include "company_func.h"
19 #include "depot_base.h"
20 #include "station_base.h"
21 #include "newgrf_engine.h"
22 #include "pathfinder/yapf/yapf.h"
23 #include "newgrf_sound.h"
24 #include "spritecache.h"
25 #include "strings_func.h"
26 #include "window_func.h"
27 #include "date_func.h"
28 #include "vehicle_func.h"
29 #include "sound_func.h"
30 #include "ai/ai.hpp"
31 #include "game/game.hpp"
33 #include "engine_base.h"
34 #include "company_base.h"
35 #include "tunnelbridge_map.h"
36 #include "zoom_func.h"
37 
38 #include "table/strings.h"
39 
40 #include "safeguards.h"
41 
48 {
49  if (HasTileWaterClass(tile)) return GetWaterClass(tile);
50  if (IsTileType(tile, MP_TUNNELBRIDGE)) {
52  return WATER_CLASS_CANAL;
53  }
54  if (IsTileType(tile, MP_RAILWAY)) {
55  assert(GetRailGroundType(tile) == RAIL_GROUND_WATER);
56  return WATER_CLASS_SEA;
57  }
58  NOT_REACHED();
59 }
60 
61 static const uint16 _ship_sprites[] = {0x0E5D, 0x0E55, 0x0E65, 0x0E6D};
62 
63 template <>
64 bool IsValidImageIndex<VEH_SHIP>(uint8 image_index)
65 {
66  return image_index < lengthof(_ship_sprites);
67 }
68 
69 static inline TrackBits GetTileShipTrackStatus(TileIndex tile)
70 {
72 }
73 
74 static void GetShipIcon(EngineID engine, EngineImageType image_type, VehicleSpriteSeq *result)
75 {
76  const Engine *e = Engine::Get(engine);
77  uint8 spritenum = e->u.ship.image_index;
78 
79  if (is_custom_sprite(spritenum)) {
80  GetCustomVehicleIcon(engine, DIR_W, image_type, result);
81  if (result->IsValid()) return;
82 
83  spritenum = e->original_image_index;
84  }
85 
86  assert(IsValidImageIndex<VEH_SHIP>(spritenum));
87  result->Set(DIR_W + _ship_sprites[spritenum]);
88 }
89 
90 void DrawShipEngine(int left, int right, int preferred_x, int y, EngineID engine, PaletteID pal, EngineImageType image_type)
91 {
92  VehicleSpriteSeq seq;
93  GetShipIcon(engine, image_type, &seq);
94 
95  Rect rect;
96  seq.GetBounds(&rect);
97  preferred_x = Clamp(preferred_x,
98  left - UnScaleGUI(rect.left),
99  right - UnScaleGUI(rect.right));
100 
101  seq.Draw(preferred_x, y, pal, pal == PALETTE_CRASH);
102 }
103 
113 void GetShipSpriteSize(EngineID engine, uint &width, uint &height, int &xoffs, int &yoffs, EngineImageType image_type)
114 {
115  VehicleSpriteSeq seq;
116  GetShipIcon(engine, image_type, &seq);
117 
118  Rect rect;
119  seq.GetBounds(&rect);
120 
121  width = UnScaleGUI(rect.right - rect.left + 1);
122  height = UnScaleGUI(rect.bottom - rect.top + 1);
123  xoffs = UnScaleGUI(rect.left);
124  yoffs = UnScaleGUI(rect.top);
125 }
126 
127 void Ship::GetImage(Direction direction, EngineImageType image_type, VehicleSpriteSeq *result) const
128 {
129  uint8 spritenum = this->spritenum;
130 
131  if (is_custom_sprite(spritenum)) {
132  GetCustomVehicleSprite(this, direction, image_type, result);
133  if (result->IsValid()) return;
134 
135  spritenum = this->GetEngine()->original_image_index;
136  }
137 
138  assert(IsValidImageIndex<VEH_SHIP>(spritenum));
139  result->Set(_ship_sprites[spritenum] + direction);
140 }
141 
142 static const Depot *FindClosestShipDepot(const Vehicle *v, uint max_distance)
143 {
144  /* Find the closest depot */
145  const Depot *depot;
146  const Depot *best_depot = NULL;
147  /* If we don't have a maximum distance, i.e. distance = 0,
148  * we want to find any depot so the best distance of no
149  * depot must be more than any correct distance. On the
150  * other hand if we have set a maximum distance, any depot
151  * further away than max_distance can safely be ignored. */
152  uint best_dist = max_distance == 0 ? UINT_MAX : max_distance + 1;
153 
154  FOR_ALL_DEPOTS(depot) {
155  TileIndex tile = depot->xy;
156  if (IsShipDepotTile(tile) && IsTileOwner(tile, v->owner)) {
157  uint dist = DistanceManhattan(tile, v->tile);
158  if (dist < best_dist) {
159  best_dist = dist;
160  best_depot = depot;
161  }
162  }
163  }
164 
165  return best_depot;
166 }
167 
168 static void CheckIfShipNeedsService(Vehicle *v)
169 {
170  if (Company::Get(v->owner)->settings.vehicle.servint_ships == 0 || !v->NeedsAutomaticServicing()) return;
171  if (v->IsChainInDepot()) {
173  return;
174  }
175 
176  uint max_distance;
178  case VPF_OPF: max_distance = 12; break;
181  default: NOT_REACHED();
182  }
183 
184  const Depot *depot = FindClosestShipDepot(v, max_distance);
185 
186  if (depot == NULL) {
187  if (v->current_order.IsType(OT_GOTO_DEPOT)) {
190  }
191  return;
192  }
193 
195  v->dest_tile = depot->xy;
197 }
198 
203 {
204  const ShipVehicleInfo *svi = ShipVehInfo(this->engine_type);
205 
206  /* Get speed fraction for the current water type. Aqueducts are always canals. */
207  bool is_ocean = GetEffectiveWaterClass(this->tile) == WATER_CLASS_SEA;
208  uint raw_speed = GetVehicleProperty(this, PROP_SHIP_SPEED, svi->max_speed);
209  this->vcache.cached_max_speed = svi->ApplyWaterClassSpeedFrac(raw_speed, is_ocean);
210 
211  /* Update cargo aging period. */
212  this->vcache.cached_cargo_age_period = GetVehicleProperty(this, PROP_SHIP_CARGO_AGE_PERIOD, EngInfo(this->engine_type)->cargo_age_period);
213 
214  this->UpdateVisualEffect();
215 }
216 
218 {
219  const Engine *e = this->GetEngine();
220  uint cost_factor = GetVehicleProperty(this, PROP_SHIP_RUNNING_COST_FACTOR, e->u.ship.running_cost);
221  return GetPrice(PR_RUNNING_SHIP, cost_factor, e->GetGRF());
222 }
223 
225 {
226  if ((++this->day_counter & 7) == 0) {
227  DecreaseVehicleValue(this);
228  }
229 
230  CheckVehicleBreakdown(this);
231  AgeVehicle(this);
232  CheckIfShipNeedsService(this);
233 
234  CheckOrders(this);
235 
236  if (this->running_ticks == 0) return;
237 
239 
240  this->profit_this_year -= cost.GetCost();
241  this->running_ticks = 0;
242 
244 
246  /* we need this for the profit */
248 }
249 
251 {
252  if (this->vehstatus & VS_CRASHED) return INVALID_TRACKDIR;
253 
254  if (this->IsInDepot()) {
255  /* We'll assume the ship is facing outwards */
256  return DiagDirToDiagTrackdir(GetShipDepotDirection(this->tile));
257  }
258 
259  if (this->state == TRACK_BIT_WORMHOLE) {
260  /* ship on aqueduct, so just use his direction and assume a diagonal track */
262  }
263 
265 }
266 
268 {
269  this->colourmap = PAL_NONE;
270  this->UpdateViewport(true, false);
271  this->UpdateCache();
272 }
273 
274 static void PlayShipSound(const Vehicle *v)
275 {
276  if (!PlayVehicleSound(v, VSE_START)) {
277  SndPlayVehicleFx(ShipVehInfo(v->engine_type)->sfx, v);
278  }
279 }
280 
282 {
283  PlayShipSound(this);
284 }
285 
287 {
288  if (station == this->last_station_visited) this->last_station_visited = INVALID_STATION;
289 
290  const Station *st = Station::Get(station);
291  if (st->dock_tile != INVALID_TILE) {
293  } else {
294  this->IncrementRealOrderIndex();
295  return 0;
296  }
297 }
298 
300 {
301  static const int8 _delta_xy_table[8][4] = {
302  /* y_extent, x_extent, y_offs, x_offs */
303  { 6, 6, -3, -3}, // N
304  { 6, 32, -3, -16}, // NE
305  { 6, 6, -3, -3}, // E
306  {32, 6, -16, -3}, // SE
307  { 6, 6, -3, -3}, // S
308  { 6, 32, -3, -16}, // SW
309  { 6, 6, -3, -3}, // W
310  {32, 6, -16, -3}, // NW
311  };
312 
313  const int8 *bb = _delta_xy_table[direction];
314  this->x_offs = bb[3];
315  this->y_offs = bb[2];
316  this->x_extent = bb[1];
317  this->y_extent = bb[0];
318  this->z_extent = 6;
319 }
320 
321 static const TileIndexDiffC _ship_leave_depot_offs[] = {
322  {-1, 0},
323  { 0, -1}
324 };
325 
326 static bool CheckShipLeaveDepot(Ship *v)
327 {
328  if (!v->IsChainInDepot()) return false;
329 
330  /* We are leaving a depot, but have to go to the exact same one; re-enter */
331  if (v->current_order.IsType(OT_GOTO_DEPOT) &&
334  return true;
335  }
336 
337  TileIndex tile = v->tile;
338  Axis axis = GetShipDepotAxis(tile);
339 
340  DiagDirection north_dir = ReverseDiagDir(AxisToDiagDir(axis));
341  TileIndex north_neighbour = TILE_ADD(tile, ToTileIndexDiff(_ship_leave_depot_offs[axis]));
342  DiagDirection south_dir = AxisToDiagDir(axis);
343  TileIndex south_neighbour = TILE_ADD(tile, -2 * ToTileIndexDiff(_ship_leave_depot_offs[axis]));
344 
345  TrackBits north_tracks = DiagdirReachesTracks(north_dir) & GetTileShipTrackStatus(north_neighbour);
346  TrackBits south_tracks = DiagdirReachesTracks(south_dir) & GetTileShipTrackStatus(south_neighbour);
347  if (north_tracks && south_tracks) {
348  /* Ask pathfinder for best direction */
349  bool reverse = false;
350  bool path_found;
352  case VPF_OPF: reverse = OPFShipChooseTrack(v, north_neighbour, north_dir, north_tracks, path_found) == INVALID_TRACK; break; // OPF always allows reversing
353  case VPF_NPF: reverse = NPFShipCheckReverse(v); break;
354  case VPF_YAPF: reverse = YapfShipCheckReverse(v); break;
355  default: NOT_REACHED();
356  }
357  if (reverse) north_tracks = TRACK_BIT_NONE;
358  }
359 
360  if (north_tracks) {
361  /* Leave towards north */
362  v->direction = DiagDirToDir(north_dir);
363  } else if (south_tracks) {
364  /* Leave towards south */
365  v->direction = DiagDirToDir(south_dir);
366  } else {
367  /* Both ways blocked */
368  return false;
369  }
370 
371  v->state = AxisToTrackBits(axis);
372  v->vehstatus &= ~VS_HIDDEN;
373 
374  v->cur_speed = 0;
375  v->UpdateViewport(true, true);
377 
378  PlayShipSound(v);
382 
383  return false;
384 }
385 
386 static bool ShipAccelerate(Vehicle *v)
387 {
388  uint spd;
389  byte t;
390 
391  spd = min(v->cur_speed + 1, v->vcache.cached_max_speed);
392  spd = min(spd, v->current_order.GetMaxSpeed() * 2);
393 
394  /* updates statusbar only if speed have changed to save CPU time */
395  if (spd != v->cur_speed) {
396  v->cur_speed = spd;
398  }
399 
400  /* Convert direction-independent speed into direction-dependent speed. (old movement method) */
401  spd = v->GetOldAdvanceSpeed(spd);
402 
403  if (spd == 0) return false;
404  if ((byte)++spd == 0) return true;
405 
406  v->progress = (t = v->progress) - (byte)spd;
407 
408  return (t < v->progress);
409 }
410 
416 static void ShipArrivesAt(const Vehicle *v, Station *st)
417 {
418  /* Check if station was ever visited before */
419  if (!(st->had_vehicle_of_type & HVOT_SHIP)) {
420  st->had_vehicle_of_type |= HVOT_SHIP;
421 
422  SetDParam(0, st->index);
424  STR_NEWS_FIRST_SHIP_ARRIVAL,
426  v->index,
427  st->index
428  );
429  AI::NewEvent(v->owner, new ScriptEventStationFirstVehicle(st->index, v->index));
430  Game::NewEvent(new ScriptEventStationFirstVehicle(st->index, v->index));
431  }
432 }
433 
434 
444 static Track ChooseShipTrack(Ship *v, TileIndex tile, DiagDirection enterdir, TrackBits tracks)
445 {
446  assert(IsValidDiagDirection(enterdir));
447 
448  bool path_found = true;
449  Track track;
451  case VPF_OPF: track = OPFShipChooseTrack(v, tile, enterdir, tracks, path_found); break;
452  case VPF_NPF: track = NPFShipChooseTrack(v, tile, enterdir, tracks, path_found); break;
453  case VPF_YAPF: track = YapfShipChooseTrack(v, tile, enterdir, tracks, path_found); break;
454  default: NOT_REACHED();
455  }
456 
457  v->HandlePathfindingResult(path_found);
458  return track;
459 }
460 
461 static const Direction _new_vehicle_direction_table[] = {
464  DIR_E , DIR_SE, DIR_S
465 };
466 
467 static Direction ShipGetNewDirectionFromTiles(TileIndex new_tile, TileIndex old_tile)
468 {
469  uint offs = (TileY(new_tile) - TileY(old_tile) + 1) * 4 +
470  TileX(new_tile) - TileX(old_tile) + 1;
471  assert(offs < 11 && offs != 3 && offs != 7);
472  return _new_vehicle_direction_table[offs];
473 }
474 
475 static Direction ShipGetNewDirection(Vehicle *v, int x, int y)
476 {
477  uint offs = (y - v->y_pos + 1) * 4 + (x - v->x_pos + 1);
478  assert(offs < 11 && offs != 3 && offs != 7);
479  return _new_vehicle_direction_table[offs];
480 }
481 
482 static inline TrackBits GetAvailShipTracks(TileIndex tile, DiagDirection dir)
483 {
484  return GetTileShipTrackStatus(tile) & DiagdirReachesTracks(dir);
485 }
486 
487 static const byte _ship_subcoord[4][6][3] = {
488  {
489  {15, 8, 1},
490  { 0, 0, 0},
491  { 0, 0, 0},
492  {15, 8, 2},
493  {15, 7, 0},
494  { 0, 0, 0},
495  },
496  {
497  { 0, 0, 0},
498  { 8, 0, 3},
499  { 7, 0, 2},
500  { 0, 0, 0},
501  { 8, 0, 4},
502  { 0, 0, 0},
503  },
504  {
505  { 0, 8, 5},
506  { 0, 0, 0},
507  { 0, 7, 6},
508  { 0, 0, 0},
509  { 0, 0, 0},
510  { 0, 8, 4},
511  },
512  {
513  { 0, 0, 0},
514  { 8, 15, 7},
515  { 0, 0, 0},
516  { 8, 15, 6},
517  { 0, 0, 0},
518  { 7, 15, 0},
519  }
520 };
521 
522 static void ShipController(Ship *v)
523 {
524  uint32 r;
525  const byte *b;
526  Direction dir;
527  Track track;
528  TrackBits tracks;
529 
530  v->tick_counter++;
531  v->current_order_time++;
532 
533  if (v->HandleBreakdown()) return;
534 
535  if (v->vehstatus & VS_STOPPED) return;
536 
537  ProcessOrders(v);
538  v->HandleLoading();
539 
540  if (v->current_order.IsType(OT_LOADING)) return;
541 
542  if (CheckShipLeaveDepot(v)) return;
543 
544  v->ShowVisualEffect();
545 
546  if (!ShipAccelerate(v)) return;
547 
549  if (v->state != TRACK_BIT_WORMHOLE) {
550  /* Not on a bridge */
551  if (gp.old_tile == gp.new_tile) {
552  /* Staying in tile */
553  if (v->IsInDepot()) {
554  gp.x = v->x_pos;
555  gp.y = v->y_pos;
556  } else {
557  /* Not inside depot */
558  r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y);
559  if (HasBit(r, VETS_CANNOT_ENTER)) goto reverse_direction;
560 
561  /* A leave station order only needs one tick to get processed, so we can
562  * always skip ahead. */
563  if (v->current_order.IsType(OT_LEAVESTATION)) {
564  v->current_order.Free();
566  } else if (v->dest_tile != 0) {
567  /* We have a target, let's see if we reached it... */
568  if (v->current_order.IsType(OT_GOTO_WAYPOINT) &&
569  DistanceManhattan(v->dest_tile, gp.new_tile) <= 3) {
570  /* We got within 3 tiles of our target buoy, so let's skip to our
571  * next order */
572  UpdateVehicleTimetable(v, true);
575  } else {
576  /* Non-buoy orders really need to reach the tile */
577  if (v->dest_tile == gp.new_tile) {
578  if (v->current_order.IsType(OT_GOTO_DEPOT)) {
579  if ((gp.x & 0xF) == 8 && (gp.y & 0xF) == 8) {
581  return;
582  }
583  } else if (v->current_order.IsType(OT_GOTO_STATION)) {
585 
586  /* Process station in the orderlist. */
588  if (st->facilities & FACIL_DOCK) { // ugly, ugly workaround for problem with ships able to drop off cargo at wrong stations
589  ShipArrivesAt(v, st);
590  v->BeginLoading();
591  } else { // leave stations without docks right aways
594  }
595  }
596  }
597  }
598  }
599  }
600  } else {
601  /* New tile */
602  if (!IsValidTile(gp.new_tile)) goto reverse_direction;
603 
604  dir = ShipGetNewDirectionFromTiles(gp.new_tile, gp.old_tile);
605  assert(dir == DIR_NE || dir == DIR_SE || dir == DIR_SW || dir == DIR_NW);
606  DiagDirection diagdir = DirToDiagDir(dir);
607  tracks = GetAvailShipTracks(gp.new_tile, diagdir);
608  if (tracks == TRACK_BIT_NONE) goto reverse_direction;
609 
610  /* Choose a direction, and continue if we find one */
611  track = ChooseShipTrack(v, gp.new_tile, diagdir, tracks);
612  if (track == INVALID_TRACK) goto reverse_direction;
613 
614  b = _ship_subcoord[diagdir][track];
615 
616  gp.x = (gp.x & ~0xF) | b[0];
617  gp.y = (gp.y & ~0xF) | b[1];
618 
619  /* Call the landscape function and tell it that the vehicle entered the tile */
620  r = VehicleEnterTile(v, gp.new_tile, gp.x, gp.y);
621  if (HasBit(r, VETS_CANNOT_ENTER)) goto reverse_direction;
622 
623  if (!HasBit(r, VETS_ENTERED_WORMHOLE)) {
624  v->tile = gp.new_tile;
625  v->state = TrackToTrackBits(track);
626 
627  /* Update ship cache when the water class changes. Aqueducts are always canals. */
630  if (old_wc != new_wc) v->UpdateCache();
631  }
632 
633  v->direction = (Direction)b[2];
634  }
635  } else {
636  /* On a bridge */
638  v->x_pos = gp.x;
639  v->y_pos = gp.y;
640  v->UpdatePosition();
641  if ((v->vehstatus & VS_HIDDEN) == 0) v->Vehicle::UpdateViewport(true);
642  return;
643  }
644  }
645 
646  /* update image of ship, as well as delta XY */
647  dir = ShipGetNewDirection(v, gp.x, gp.y);
648  v->x_pos = gp.x;
649  v->y_pos = gp.y;
650  v->z_pos = GetSlopePixelZ(gp.x, gp.y);
651 
652 getout:
653  v->UpdatePosition();
654  v->UpdateViewport(true, true);
655  return;
656 
657 reverse_direction:
658  dir = ReverseDir(v->direction);
659  v->direction = dir;
660  goto getout;
661 }
662 
664 {
665  if (!(this->vehstatus & VS_STOPPED)) this->running_ticks++;
666 
667  ShipController(this);
668 
669  return true;
670 }
671 
681 CommandCost CmdBuildShip(TileIndex tile, DoCommandFlag flags, const Engine *e, uint16 data, Vehicle **ret)
682 {
683  tile = GetShipDepotNorthTile(tile);
684  if (flags & DC_EXEC) {
685  int x;
686  int y;
687 
688  const ShipVehicleInfo *svi = &e->u.ship;
689 
690  Ship *v = new Ship();
691  *ret = v;
692 
693  v->owner = _current_company;
694  v->tile = tile;
695  x = TileX(tile) * TILE_SIZE + TILE_SIZE / 2;
696  y = TileY(tile) * TILE_SIZE + TILE_SIZE / 2;
697  v->x_pos = x;
698  v->y_pos = y;
699  v->z_pos = GetSlopePixelZ(x, y);
700 
701  v->UpdateDeltaXY(v->direction);
703 
704  v->spritenum = svi->image_index;
706  v->cargo_cap = svi->capacity;
707  v->refit_cap = 0;
708 
709  v->last_station_visited = INVALID_STATION;
710  v->last_loading_station = INVALID_STATION;
711  v->engine_type = e->index;
712 
713  v->reliability = e->reliability;
715  v->max_age = e->GetLifeLengthInDays();
716  _new_vehicle_id = v->index;
717 
718  v->state = TRACK_BIT_DEPOT;
719 
720  v->SetServiceInterval(Company::Get(_current_company)->settings.vehicle.servint_ships);
722  v->build_year = _cur_year;
723  v->sprite_seq.Set(SPR_IMG_QUERY);
725 
726  v->UpdateCache();
727 
729  v->SetServiceIntervalIsPercent(Company::Get(_current_company)->settings.vehicle.servint_ispercent);
730 
732 
733  v->cargo_cap = e->DetermineCapacity(v);
734 
736 
737  v->UpdatePosition();
738  }
739 
740  return CommandCost();
741 }
742 
743 bool Ship::FindClosestDepot(TileIndex *location, DestinationID *destination, bool *reverse)
744 {
745  const Depot *depot = FindClosestShipDepot(this, 0);
746 
747  if (depot == NULL) return false;
748 
749  if (location != NULL) *location = depot->xy;
750  if (destination != NULL) *destination = depot->index;
751 
752  return true;
753 }