From 267314b4d0a28ac8a9d5b0d44a7c6dcd7ef88ac1 Mon Sep 17 00:00:00 2001 From: peter1138 Date: Sat, 5 Nov 2005 16:07:26 +0000 Subject: (svn r3139) -NewGRF, Feature: support for articulated rail vehicles. This is used, for example, by coal tenders. --- engine.c | 2 + engine.h | 3 + train_cmd.c | 208 +++++++++++++++++++++++++++++++++++++++++++++--------------- train_gui.c | 17 +++-- vehicle.c | 28 +++++--- vehicle.h | 36 +++++++++++ 6 files changed, 230 insertions(+), 64 deletions(-) diff --git a/engine.c b/engine.c index 2ce2a21a7..2018734c4 100644 --- a/engine.c +++ b/engine.c @@ -375,6 +375,8 @@ static const SpriteGroup* ResolveVehicleSpriteGroup(const SpriteGroup *spritegro if (dsg->variable == 0x0C) { /* Callback ID */ value = callback_info & 0xFF; + } else if (dsg->variable == 0x10) { + value = (callback_info >> 8) & 0xFF; } else if ((dsg->variable >> 6) == 0) { /* General property */ value = GetDeterministicSpriteValue(dsg->variable); diff --git a/engine.h b/engine.h index 7ec4304cb..77ac8c216 100644 --- a/engine.h +++ b/engine.h @@ -153,6 +153,8 @@ enum CallbackID { // Refit capacity, the passed vehicle needs to have its ->cargo_type set to // the cargo we are refitting to, returns the new cargo capacity CBID_REFIT_CAP = 0x15, + + CBID_ARTIC_ENGINE = 0x16, }; // bit positions for rvi->callbackmask, indicates which callbacks are used by an engine @@ -161,6 +163,7 @@ enum CallbackMask { CBM_WAGON_POWER = 0, CBM_VEH_LENGTH = 1, CBM_REFIT_CAP = 3, + CBM_ARTIC_ENGINE = 4, }; enum { diff --git a/train_cmd.c b/train_cmd.c index 7ca2ba5db..21953870f 100644 --- a/train_cmd.c +++ b/train_cmd.c @@ -46,12 +46,17 @@ void TrainCargoChanged(Vehicle *v) { const RailVehicleInfo *rvi = RailVehInfo(u->engine_type); uint16 vweight = 0; - // vehicle weight is the sum of the weight of the vehicle and the weight of its cargo - vweight += rvi->weight; vweight += (_cargoc.weights[u->cargo_type] * u->cargo_count) / 16; - // powered wagons have extra weight added - if (HASBIT(u->u.rail.flags, VRF_POWEREDWAGON)) - vweight += RailVehInfo(v->engine_type)->pow_wag_weight; + + // Vehicle weight is not added for articulated parts. + if (u->subtype != TS_Artic_Part) { + // vehicle weight is the sum of the weight of the vehicle and the weight of its cargo + vweight += rvi->weight; + + // powered wagons have extra weight added + if (HASBIT(u->u.rail.flags, VRF_POWEREDWAGON)) + vweight += RailVehInfo(v->engine_type)->pow_wag_weight; + } // consist weight is the sum of the weight of all vehicles in the consist weight += vweight; @@ -92,14 +97,11 @@ void TrainConsistChanged(Vehicle *v) { // update the 'first engine' u->u.rail.first_engine = (v == u) ? INVALID_VEHICLE : first_engine; - // power is the sum of the powers of all engines and powered wagons in the consist - power += rvi_u->power; - if (rvi_u->visual_effect != 0) { u->u.rail.cached_vis_effect = rvi_u->visual_effect; } else { - if (rvi_u->flags & RVI_WAGON) { - // Wagons have no effect by default + if (rvi_u->flags & RVI_WAGON || u->subtype == TS_Artic_Part) { + // Wagons and articulated parts have no effect by default u->u.rail.cached_vis_effect = 0x40; } else if (rvi_u->engclass == 0) { // Steam is offset by -4 units @@ -110,27 +112,32 @@ void TrainConsistChanged(Vehicle *v) { } } - // check if its a powered wagon - CLRBIT(u->u.rail.flags, VRF_POWEREDWAGON); - if ((rvi_v->pow_wag_power != 0) && (rvi_u->flags & RVI_WAGON) && UsesWagonOverride(u)) { - if (HASBIT(rvi_u->callbackmask, CBM_WAGON_POWER)) { - uint16 callback = GetCallBackResult(CBID_WAGON_POWER, u->engine_type, u); + if (u->subtype != TS_Artic_Part) { + // power is the sum of the powers of all engines and powered wagons in the consist + power += rvi_u->power; - if (callback != CALLBACK_FAILED) - u->u.rail.cached_vis_effect = callback; - } + // check if its a powered wagon + CLRBIT(u->u.rail.flags, VRF_POWEREDWAGON); + if ((rvi_v->pow_wag_power != 0) && (rvi_u->flags & RVI_WAGON) && UsesWagonOverride(u)) { + if (HASBIT(rvi_u->callbackmask, CBM_WAGON_POWER)) { + uint16 callback = GetCallBackResult(CBID_WAGON_POWER, u->engine_type, u); - if (u->u.rail.cached_vis_effect < 0x40) { - /* wagon is powered */ - SETBIT(u->u.rail.flags, VRF_POWEREDWAGON); // cache 'powered' status - power += rvi_v->pow_wag_power; + if (callback != CALLBACK_FAILED) + u->u.rail.cached_vis_effect = callback; + } + + if (u->u.rail.cached_vis_effect < 0x40) { + /* wagon is powered */ + SETBIT(u->u.rail.flags, VRF_POWEREDWAGON); // cache 'powered' status + power += rvi_v->pow_wag_power; + } } - } - // max speed is the minimum of the speed limits of all vehicles in the consist - if (!(rvi_u->flags & RVI_WAGON) || _patches.wagon_speed_limits) - if (rvi_u->max_speed != 0 && !UsesWagonOverride(u)) - max_speed = min(rvi_u->max_speed, max_speed); + // max speed is the minimum of the speed limits of all vehicles in the consist + if (!(rvi_u->flags & RVI_WAGON) || _patches.wagon_speed_limits) + if (rvi_u->max_speed != 0 && !UsesWagonOverride(u)) + max_speed = min(rvi_u->max_speed, max_speed); + } // check the vehicle length (callback) veh_len = CALLBACK_FAILED; @@ -418,6 +425,78 @@ void DrawTrainEngine(int x, int y, EngineID engine, uint32 image_ormod) DrawSprite(image | image_ormod, x, y); } +static uint CountArticulatedParts(const RailVehicleInfo *rvi, EngineID engine_type) +{ + uint16 callback; + uint i; + + if (!HASBIT(rvi->callbackmask, CBM_ARTIC_ENGINE)) + return 0; + + for (i = 1; i < 10; i++) { + callback = GetCallBackResult(CBID_ARTIC_ENGINE + (i << 8), engine_type, NULL); + if (callback == CALLBACK_FAILED || callback == 0xFF) + break; + } + + return i; +} + +static void AddArticulatedParts(const RailVehicleInfo *rvi, Vehicle **vl) +{ + const RailVehicleInfo *rvi_artic; + EngineID engine_type; + Vehicle *v = vl[0]; + Vehicle *u = v; + uint16 callback; + bool flip_image; + uint i; + + if (!HASBIT(rvi->callbackmask, CBM_ARTIC_ENGINE)) + return; + + for (i = 1; i < 10; i++) { + callback = GetCallBackResult(CBID_ARTIC_ENGINE + (i << 8), v->engine_type, NULL); + if (callback == CALLBACK_FAILED || callback == 0xFF) + return; + + u->next = vl[i]; + u = u->next; + + engine_type = GB(callback, 0, 6); + flip_image = HASBIT(callback, 7); + rvi_artic = RailVehInfo(engine_type); + + // get common values from first engine + u->direction = v->direction; + u->owner = v->owner; + u->tile = v->tile; + u->x_pos = v->x_pos; + u->y_pos = v->y_pos; + u->z_pos = v->z_pos; + u->z_height = v->z_height; + u->u.rail.track = v->u.rail.track; + u->u.rail.railtype = v->u.rail.railtype; + u->build_year = v->build_year; + u->vehstatus = v->vehstatus & ~VS_STOPPED; + u->u.rail.first_engine = v->engine_type; + + // get more settings from rail vehicle info + u->spritenum = rvi_artic->image_index; + if (flip_image) u->spritenum++; + u->cargo_type = rvi_artic->cargo_type; + u->cargo_cap = rvi_artic->capacity; + u->max_speed = 0; + u->max_age = 0; + u->engine_type = engine_type; + u->value = 0; + u->type = VEH_Train; + u->subtype = TS_Artic_Part; + u->cur_image = 0xAC2; + + VehiclePositionChanged(u); + } +} static int32 CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags) { @@ -426,24 +505,27 @@ static int32 CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags) const RailVehicleInfo *rvi; const Engine *e; int x,y; + uint num_vehicles; SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES); rvi = RailVehInfo(engine); value = (rvi->base_cost * _price.build_railwagon) >> 8; + num_vehicles = 1 + CountArticulatedParts(rvi, engine); + if (!(flags & DC_QUERY_COST)) { - _error_message = STR_00E1_TOO_MANY_VEHICLES_IN_GAME; + Vehicle *vl[num_vehicles]; - v = AllocateVehicle(); - if (v == NULL) - return CMD_ERROR; + if (!AllocateVehicles(vl, num_vehicles)) + return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); if (flags & DC_EXEC) { byte img = rvi->image_index; Vehicle *u, *w; uint dir; + v = vl[0]; v->spritenum = img; u = NULL; @@ -492,6 +574,8 @@ static int32 CmdBuildRailWagon(EngineID engine, TileIndex tile, uint32 flags) v->type = VEH_Train; v->cur_image = 0xAC2; + AddArticulatedParts(rvi, vl); + _new_wagon_id = v->index; _new_vehicle_id = v->index; @@ -581,10 +665,11 @@ int32 CmdBuildRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) { const RailVehicleInfo *rvi; int value; - Vehicle *v, *u; + Vehicle *v; UnitID unit_num; Engine *e; TileIndex tile = TileVirtXY(x, y); + uint num_vehicles; /* Check if the engine-type is valid (for the player) */ if (!IsEngineBuildable(p1, VEH_Train)) return CMD_ERROR; @@ -612,12 +697,16 @@ int32 CmdBuildRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) if (rvi->flags&RVI_MULTIHEAD && HASBIT(p2,0)) value /= 2; + num_vehicles = (rvi->flags & RVI_MULTIHEAD && HASBIT(p2, 0)) ? 2 : 1; + num_vehicles += CountArticulatedParts(rvi, p1); if (!(flags & DC_QUERY_COST)) { - v = AllocateVehicle(); - if (v == NULL || IsOrderPoolFull()) + Vehicle *vl[num_vehicles]; + if (!AllocateVehicles(vl, num_vehicles) || IsOrderPoolFull()) return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); + v = vl[0]; + unit_num = GetFreeUnitNumber(VEH_Train); if (unit_num > _patches.max_trains) return_cmd_error(STR_00E1_TOO_MANY_VEHICLES_IN_GAME); @@ -665,8 +754,10 @@ int32 CmdBuildRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) VehiclePositionChanged(v); - if (rvi->flags&RVI_MULTIHEAD && (u = AllocateVehicle()) != NULL && !HASBIT(p2,0)) { - AddRearEngineToMultiheadedTrain(v, u, true); + if (rvi->flags & RVI_MULTIHEAD && !HASBIT(p2, 0)) { + AddRearEngineToMultiheadedTrain(vl[0], vl[1], true); + } else { + AddArticulatedParts(rvi, vl); } TrainConsistChanged(v); @@ -716,23 +807,27 @@ int CheckTrainStoppedInDepot(const Vehicle *v) return count; } -// unlink a rail wagon from the linked list. -// returns the new value of first +/** + * Unlink a rail wagon from the consist. + * @param v Vehicle to remove. + * @param first The first vehicle of the consist. + * @return The first vehicle of the consist. + */ static Vehicle *UnlinkWagon(Vehicle *v, Vehicle *first) { Vehicle *u; // unlinking the first vehicle of the chain? if (v == first) { - v = v->next; + v = GetNextVehicle(v); if (v == NULL) return NULL; v->subtype = TS_Free_Car; return v; } - for (u = first; u->next != v; u = u->next) {} - u->next = v->next; + for (u = first; GetNextVehicle(u) != v; u = GetNextVehicle(u)) {} + GetLastEnginePart(u)->next = GetNextVehicle(v); return first; } @@ -788,6 +883,12 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) dst = GetVehicle(d); } + // if an articulated part is being handled, deal with its parent vehicle + while (src->subtype == TS_Artic_Part) src = GetPrevVehicleInChain(src); + if (dst != NULL) { + while (dst->subtype == TS_Artic_Part) dst = GetPrevVehicleInChain(dst); + } + // don't move the same vehicle.. if (src == dst) return 0; @@ -798,7 +899,11 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) /* locate the head of the two chains */ src_head = GetFirstVehicleInChain(src); dst_head = NULL; - if (dst != NULL) dst_head = GetFirstVehicleInChain(dst); + if (dst != NULL) { + dst_head = GetFirstVehicleInChain(dst); + // Now deal with articulated part of destination wagon + dst = GetLastEnginePart(dst); + } /* clear the ->first cache */ { @@ -852,8 +957,8 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) // unlink ALL wagons if (src != src_head) { Vehicle *v = src_head; - while (v->next != src) v=v->next; - v->next = NULL; + while (GetNextVehicle(v) != src) v = GetNextVehicle(v); + GetLastEnginePart(v)->next = NULL; } else { src_head = NULL; } @@ -863,7 +968,7 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) dst_head = NULL; // unlink single wagon from linked list src_head = UnlinkWagon(src, src_head); - src->next = NULL; + GetLastEnginePart(src)->next = NULL; } if (dst == NULL) { @@ -893,8 +998,8 @@ int32 CmdMoveRailVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) { Vehicle *v; - for (v = src; v->next != NULL; v = v->next) {}; - v->next = dst->next; + for (v = src; GetNextVehicle(v) != NULL; v = GetNextVehicle(v)); + GetLastEnginePart(v)->next = dst->next; } dst->next = src; } @@ -999,6 +1104,7 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) SET_EXPENSES_TYPE(EXPENSES_NEW_VEHICLES); + while (v->subtype == TS_Artic_Part) v = GetPrevVehicleInChain(v); first = GetFirstVehicleInChain(v); // make sure the vehicle is stopped in the depot @@ -1032,7 +1138,7 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) /* 2. We are selling the first engine, some special action might be required * here, so take attention */ if ((flags & DC_EXEC) && v == first) { - Vehicle *new_f = first->next; + Vehicle *new_f = GetNextVehicle(first); /* 2.1 If the first wagon is sold, update the first-> pointers to NULL */ for (tmp = first; tmp != NULL; tmp = tmp->next) tmp->first = NULL; @@ -1082,7 +1188,7 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) * engines to its train anyways */ if (p2 == 2 && ori_subtype == TS_Front_Engine) { for (v = first; v != NULL; v = tmp) { - tmp = v->next; + tmp = GetNextVehicle(v); DoCommandByTile(v->tile, v->index | INVALID_VEHICLE << 16, 0, DC_EXEC, CMD_MOVE_RAIL_VEHICLE); } } @@ -1093,7 +1199,7 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) * to be able to deduce which ones go with which ones */ int enf_count = 0; int enr_count = 0; - for (tmp = first; tmp != NULL; tmp = tmp->next) { + for (tmp = first; tmp != NULL; tmp = GetNextVehicle(tmp)) { if (RailVehInfo(tmp->engine_type)->flags & RVI_MULTIHEAD) (IS_FIRSTHEAD_SPRITE(tmp->spritenum)) ? enf_count++ : enr_count++; } @@ -1102,7 +1208,7 @@ int32 CmdSellRailWagon(int x, int y, uint32 flags, uint32 p1, uint32 p2) * If we encounter a matching rear-engine to a front-engine * earlier in the chain (before deletion), leave it alone */ for (; v != NULL; v = tmp) { - tmp = v->next; + tmp = GetNextVehicle(v); if (RailVehInfo(v->engine_type)->flags & RVI_MULTIHEAD) { if (IS_FIRSTHEAD_SPRITE(v->spritenum)) { diff --git a/train_gui.c b/train_gui.c index 9af16a1ee..ea347f2b2 100644 --- a/train_gui.c +++ b/train_gui.c @@ -530,6 +530,9 @@ found_it: if (v == NULL) break; } + // if an articulated part was selected, find its parent + while (v != NULL && v->subtype == TS_Artic_Part) v = GetPrevVehicleInChain(v); + d->wagon = v; return 0; @@ -1136,7 +1139,7 @@ static void DrawTrainDetailsWindow(Window *w) tot_cargo[u->cargo_type][0] += u->cargo_count; tot_cargo[u->cargo_type][1] += u->cargo_cap; } - } while ( (u = u->next) != NULL); + } while ((u = GetNextVehicle(u)) != NULL); /* set scroll-amount seperately from counting, as to not compute num double for more carriages of the same type @@ -1197,11 +1200,17 @@ static void DrawTrainDetailsWindow(Window *w) if (det_tab != 3) { for(;;) { if (--sel < 0 && sel >= -6) { - DrawTrainImage(v, x, y, 1, 0, INVALID_VEHICLE); - _train_details_drawer_proc[WP(w,traindetails_d).tab](v, x + 30, y + 2); + int dx = 0; + u = v; + do { + DrawTrainImage(u, x + WagonLengthToPixels(dx), y, 1, 0, INVALID_VEHICLE); + dx += u->u.rail.cached_veh_length; + u = u->next; + } while (u != NULL && u->subtype == TS_Artic_Part); + _train_details_drawer_proc[WP(w,traindetails_d).tab](v, x + WagonLengthToPixels(dx) + 2, y + 2); y += 14; } - if ( (v=v->next) == NULL) + if ((v = GetNextVehicle(v)) == NULL) return; } } diff --git a/vehicle.c b/vehicle.c index f63368912..bcb084e1d 100644 --- a/vehicle.c +++ b/vehicle.c @@ -519,20 +519,28 @@ int CountVehiclesInChain(Vehicle *v) void DeleteVehicle(Vehicle *v) { - DeleteName(v->string_id); - v->type = 0; - UpdateVehiclePosHash(v, INVALID_COORD, 0); - v->next_hash = INVALID_VEHICLE; + Vehicle *u; + bool has_artic_part = false; + + do { + u = v->next; + has_artic_part = EngineHasArticPart(v); + DeleteName(v->string_id); + v->type = 0; + UpdateVehiclePosHash(v, INVALID_COORD, 0); + v->next_hash = INVALID_VEHICLE; - if (v->orders != NULL) - DeleteVehicleOrders(v); + if (v->orders != NULL) + DeleteVehicleOrders(v); + v = u; + } while (v != NULL && has_artic_part); } void DeleteVehicleChain(Vehicle *v) { do { Vehicle *u = v; - v = v->next; + v = GetNextVehicle(v); DeleteVehicle(u); } while (v != NULL); } @@ -1486,6 +1494,8 @@ static Vehicle *GetNextEnginePart(Vehicle *v) const RailVehicleInfo *rvi = RailVehInfo(v->engine_type); if (rvi->flags & RVI_MULTIHEAD) return GetRearEngine(v, v->engine_type); + if (v->next != NULL && v->next->subtype == TS_Artic_Part) + return v->next; } break; case VEH_Aircraft: @@ -1579,7 +1589,7 @@ int32 CmdCloneVehicle(int x, int y, uint32 flags, uint32 p1, uint32 p2) } w_rear = w; // trains needs to know the last car in the train, so they can add more in next loop } - } while (v->type == VEH_Train && (v=v->next) != NULL); + } while (v->type == VEH_Train && (v = GetNextVehicle(v)) != NULL); if (flags & DC_EXEC) { v = v_front; @@ -1755,7 +1765,7 @@ static void MaybeReplaceVehicle(Vehicle *v) break; cost += temp_cost; - } while (w->type == VEH_Train && (w=w->next) != NULL); + } while (w->type == VEH_Train && (w = GetNextVehicle(w)) != NULL); if (!(flags & DC_EXEC) && (CmdFailed(temp_cost) || p->money64 < (int32)(cost + p->engine_renew_money) || cost == 0)) { if (p->money64 < (int32)(cost + p->engine_renew_money) && ( _local_player == v->owner ) && cost != 0) { diff --git a/vehicle.h b/vehicle.h index be2b120d4..91921d4ba 100644 --- a/vehicle.h +++ b/vehicle.h @@ -30,6 +30,7 @@ enum VehStatus { // 1 and 3 do not appear to be used typedef enum TrainSubtypes { TS_Front_Engine = 0, // Leading engine of a train + TS_Artic_Part = 1, // Articulated part of an engine TS_Not_First = 2, // Wagon or additional engine TS_Free_Car = 4, // First in a wagon chain (in depot) } TrainSubtype; @@ -414,6 +415,41 @@ static inline bool IsVehicleIndex(uint index) return index < GetVehiclePoolSize(); } +/** + * Get the next real (non-articulated part) vehicle in the consist. + * @param v Vehicle. + * @return Next vehicle in the consist. + */ +static inline Vehicle *GetNextVehicle(const Vehicle *v) +{ + Vehicle *u = v->next; + while (u != NULL && u->subtype == TS_Artic_Part) { + u = u->next; + } + return u; +} + +/** + * Check if an engine has an articulated part. + * @param v Vehicle. + * @return True if the engine has an articulated part. + */ +static inline bool EngineHasArticPart(const Vehicle *v) +{ + return (v->next != NULL && v->next->subtype == TS_Artic_Part); +} + +/** + * Get the last part of a multi-part engine. + * @param v Vehicle. + * @return Last part of the engine. + */ +static inline Vehicle *GetLastEnginePart(Vehicle *v) +{ + while (EngineHasArticPart(v)) v = v->next; + return v; +} + /* Returns order 'index' of a vehicle or NULL when it doesn't exists */ static inline Order *GetVehicleOrder(const Vehicle *v, int index) { -- cgit v1.2.3-70-g09d2