truelight@0: #include "stdafx.h" truelight@0: #include "ttd.h" tron@1299: #include "debug.h" tron@507: #include "table/strings.h" truelight@0: #include "engine.h" truelight@0: #include "table/engines.h" tron@1009: #include "gfx.h" truelight@0: #include "player.h" truelight@0: #include "command.h" truelight@0: #include "vehicle.h" truelight@0: #include "news.h" truelight@0: #include "saveload.h" darkvater@405: #include "sprite.h" truelight@0: truelight@0: #define UPDATE_PLAYER_RAILTYPE(e,p) if ((byte)(e->railtype + 1) > p->max_railtype) p->max_railtype = e->railtype + 1; truelight@0: truelight@0: enum { truelight@0: ENGINE_AVAILABLE = 1, truelight@0: ENGINE_INTRODUCING = 2, truelight@0: ENGINE_PREVIEWING = 4, truelight@0: }; truelight@0: truelight@0: /* This maps per-landscape cargo ids to globally unique cargo ids usable ie. in truelight@0: * the custom GRF files. It is basically just a transcribed table from truelight@0: * TTDPatch's newgrf.txt. */ truelight@0: byte _global_cargo_id[NUM_LANDSCAPE][NUM_CARGO] = { truelight@0: /* LT_NORMAL */ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12 }, truelight@0: /* LT_HILLY */ { 0, 1, 2, 3, 4, 5, 6, 7, 28, 11, 10, 12 }, truelight@0: /* LT_DESERT */ { 0, 16, 2, 3, 13, 5, 6, 7, 14, 15, 10, 12 }, truelight@0: /* LT_CANDY */ { 0, 17, 2, 18, 19, 20, 21, 22, 23, 24, 25, 26 }, truelight@0: // 27 is paper in temperate climate in TTDPatch truelight@0: // Following can be renumbered: truelight@0: // 29 is the default cargo for the purpose of spritesets truelight@0: // 30 is the purchase list image (the equivalent of 0xff) for the purpose of spritesets truelight@0: }; truelight@0: truelight@0: /* These two arrays provide a reverse mapping. */ truelight@0: byte _local_cargo_id_ctype[NUM_CID] = { truelight@0: CT_PASSENGERS, CT_COAL, CT_MAIL, CT_OIL, CT_LIVESTOCK, CT_GOODS, CT_GRAIN, CT_WOOD, // 0-7 truelight@0: CT_IRON_ORE, CT_STEEL, CT_VALUABLES, CT_PAPER, CT_FOOD, CT_FRUIT, CT_COPPER_ORE, CT_WATER, // 8-15 truelight@0: CT_RUBBER, CT_SUGAR, CT_TOYS, CT_BATTERIES, CT_CANDY, CT_TOFFEE, CT_COLA, CT_COTTON_CANDY, // 16-23 truelight@0: CT_BUBBLES, CT_PLASTIC, CT_FIZZY_DRINKS, CT_PAPER /* unsup. */, CT_HILLY_UNUSED // 24-28 truelight@0: }; truelight@0: truelight@0: /* LT'th bit is set of the particular landscape if cargo available there. truelight@0: * 1: LT_NORMAL, 2: LT_HILLY, 4: LT_DESERT, 8: LT_CANDY */ truelight@0: byte _local_cargo_id_landscape[NUM_CID] = { truelight@0: 15, 3, 15, 7, 3, 7, 7, 7, 1, 1, 7, 2, 7, // 0-12 truelight@0: 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 1, 2, // 13-28 truelight@0: }; truelight@0: truelight@0: void ShowEnginePreviewWindow(int engine); truelight@0: tron@1093: void DeleteCustomEngineNames(void) truelight@0: { truelight@0: uint i; truelight@0: StringID old; truelight@0: Darkvater@1474: for (i = 0; i != TOTAL_NUM_ENGINES; i++) { truelight@0: old = _engine_name_strings[i]; truelight@0: _engine_name_strings[i] = i + STR_8000_KIRBY_PAUL_TANK_STEAM; truelight@0: DeleteName(old); truelight@0: } truelight@0: truelight@0: _vehicle_design_names &= ~1; truelight@0: } truelight@0: tron@1093: void LoadCustomEngineNames(void) truelight@0: { truelight@0: // XXX: not done */ truelight@0: DEBUG(misc, 1) ("LoadCustomEngineNames: not done"); truelight@0: } truelight@0: tron@1093: static void SetupEngineNames(void) truelight@0: { Darkvater@1474: StringID *name; truelight@0: Darkvater@1474: for (name = _engine_name_strings; name != endof(_engine_name_strings); name++) Darkvater@1474: *name = STR_SV_EMPTY; truelight@0: truelight@0: DeleteCustomEngineNames(); truelight@0: LoadCustomEngineNames(); truelight@0: } truelight@0: tron@1093: static void AdjustAvailAircraft(void) truelight@0: { truelight@0: uint16 date = _date; truelight@0: byte avail = 0; truelight@0: if (date >= 12784) avail |= 2; // big airport truelight@0: if (date < 14610 || _patches.always_small_airport) avail |= 1; // small airport truelight@0: if (date >= 15706) avail |= 4; // enable heliport truelight@0: truelight@0: if (avail != _avail_aircraft) { truelight@0: _avail_aircraft = avail; truelight@0: InvalidateWindow(WC_BUILD_STATION, 0); truelight@0: } truelight@0: } truelight@0: truelight@0: static void CalcEngineReliability(Engine *e) truelight@0: { truelight@0: uint age = e->age; truelight@0: truelight@0: if (age < e->duration_phase_1) { truelight@0: uint start = e->reliability_start; truelight@0: e->reliability = age * (e->reliability_max - start) / e->duration_phase_1 + start; truelight@0: } else if ((age -= e->duration_phase_1) < e->duration_phase_2) { truelight@0: e->reliability = e->reliability_max; truelight@0: } else if ((age -= e->duration_phase_2) < e->duration_phase_3) { truelight@0: uint max = e->reliability_max; truelight@0: e->reliability = (int)age * (int)(e->reliability_final - max) / e->duration_phase_3 + max; truelight@0: } else { dominik@292: // time's up for this engine dominik@292: // make it either available to all players (if never_expire_vehicles is enabled and if it was available earlier) dominik@292: // or disable this engine completely dominik@292: e->player_avail = (_patches.never_expire_vehicles && e->player_avail)? -1 : 0; truelight@0: e->reliability = e->reliability_final; truelight@0: } truelight@0: } truelight@0: tron@1093: void AddTypeToEngines(void) bjarni@842: { bjarni@842: Engine *e; bjarni@842: uint32 counter = 0; tron@915: bjarni@842: for(e=_engines; e != endof(_engines); e++, counter++) { bjarni@842: bjarni@842: e->type = VEH_Train; bjarni@842: if (counter >= ROAD_ENGINES_INDEX) { bjarni@842: e->type = VEH_Road; bjarni@842: if (counter >= SHIP_ENGINES_INDEX) { bjarni@842: e->type = VEH_Ship; bjarni@842: if (counter >= AIRCRAFT_ENGINES_INDEX) { bjarni@842: e->type = VEH_Aircraft; bjarni@842: if (counter >= TOTAL_NUM_ENGINES) { bjarni@842: e->type = VEH_Special; bjarni@842: } bjarni@842: } bjarni@842: } bjarni@842: } bjarni@842: } bjarni@842: } bjarni@842: tron@1093: void StartupEngines(void) truelight@0: { truelight@0: Engine *e; truelight@0: const EngineInfo *ei; bjarni@819: uint32 r, counter = 0; truelight@0: truelight@0: SetupEngineNames(); truelight@0: bjarni@842: for(e=_engines, ei=_engine_info; e != endof(_engines); e++, ei++, counter++) { truelight@0: truelight@0: e->age = 0; truelight@0: e->railtype = ei->railtype_climates >> 4; truelight@0: e->flags = 0; truelight@0: e->player_avail = 0; truelight@193: truelight@0: r = Random(); truelight@0: e->intro_date = (uint16)((r & 0x1FF) + ei->base_intro); truelight@0: if (e->intro_date <= _date) { truelight@0: e->age = (_date - e->intro_date) >> 5; truelight@0: e->player_avail = (byte)-1; truelight@0: e->flags |= ENGINE_AVAILABLE; truelight@0: } truelight@0: truelight@0: e->reliability_start = (uint16)(((r >> 16) & 0x3fff) + 0x7AE0); truelight@0: r = Random(); truelight@0: e->reliability_max = (uint16)((r & 0x3fff) + 0xbfff); truelight@0: e->reliability_final = (uint16)(((r>>16) & 0x3fff) + 0x3fff); truelight@0: truelight@0: r = Random(); truelight@0: e->duration_phase_1 = (uint16)((r & 0x1F) + 7); truelight@0: e->duration_phase_2 = (uint16)(((r >> 5) & 0xF) + ei->base_life * 12 - 96); truelight@0: e->duration_phase_3 = (uint16)(((r >> 9) & 0x7F) + 120); truelight@0: truelight@0: e->reliability_spd_dec = (ei->unk2&0x7F) << 2; truelight@0: truelight@0: /* my invented flag for something that is a wagon */ truelight@0: if (ei->unk2 & 0x80) { truelight@0: e->age = 0xFFFF; truelight@0: } else { truelight@0: CalcEngineReliability(e); truelight@0: } truelight@0: truelight@0: e->lifelength = ei->lifelength + _patches.extend_vehicle_life; truelight@0: truelight@0: // prevent certain engines from ever appearing. truelight@0: if (!HASBIT(ei->railtype_climates, _opt.landscape)) { truelight@0: e->flags |= ENGINE_AVAILABLE; truelight@0: e->player_avail = 0; truelight@0: } tron@915: bjarni@819: /* This sets up type for the engine bjarni@819: It is needed if you want to ask the engine what type it is bjarni@819: It should hopefully be the same as when you ask a vehicle what it is bjarni@819: but using this, you can ask what type an engine number is bjarni@819: even if it is not a vehicle (yet)*/ truelight@0: } truelight@0: truelight@0: AdjustAvailAircraft(); truelight@0: } truelight@0: Darkvater@1474: uint32 _engine_refit_masks[TOTAL_NUM_ENGINES]; truelight@0: truelight@0: truelight@0: // TODO: We don't support cargo-specific wagon overrides. Pretty exotic... ;-) --pasky truelight@0: tron@1477: typedef struct WagonOverride { truelight@0: byte *train_id; truelight@0: int trains; tron@1477: SpriteGroup group; tron@1477: } WagonOverride; truelight@0: tron@1477: typedef struct WagonOverrides { truelight@0: int overrides_count; tron@1477: WagonOverride *overrides; tron@1477: } WagonOverrides; truelight@0: tron@1477: static WagonOverrides _engine_wagon_overrides[TOTAL_NUM_ENGINES]; tron@1477: tron@1477: void SetWagonOverrideSprites(byte engine, SpriteGroup *group, byte *train_id, tron@1477: int trains) truelight@0: { tron@1477: WagonOverrides *wos; tron@1477: WagonOverride *wo; truelight@0: truelight@0: wos = &_engine_wagon_overrides[engine]; truelight@0: wos->overrides_count++; truelight@0: wos->overrides = realloc(wos->overrides, tron@1477: wos->overrides_count * sizeof(*wos->overrides)); truelight@193: truelight@0: wo = &wos->overrides[wos->overrides_count - 1]; darkvater@408: /* FIXME: If we are replacing an override, release original SpriteGroup darkvater@408: * to prevent leaks. But first we need to refcount the SpriteGroup. darkvater@408: * --pasky */ darkvater@369: wo->group = *group; truelight@0: wo->trains = trains; truelight@0: wo->train_id = malloc(trains); truelight@0: memcpy(wo->train_id, train_id, trains); truelight@0: } truelight@0: tron@1477: static SpriteGroup *GetWagonOverrideSpriteSet(byte engine, byte overriding_engine) truelight@0: { tron@1477: WagonOverrides *wos = &_engine_wagon_overrides[engine]; truelight@0: int i; truelight@0: darkvater@408: // XXX: This could turn out to be a timesink on profiles. We could darkvater@408: // always just dedicate 65535 bytes for an [engine][train] trampoline darkvater@408: // for O(1). Or O(logMlogN) and searching binary tree or smt. like darkvater@408: // that. --pasky truelight@0: truelight@0: for (i = 0; i < wos->overrides_count; i++) { tron@1477: WagonOverride *wo = &wos->overrides[i]; truelight@0: int j; truelight@0: truelight@0: for (j = 0; j < wo->trains; j++) { truelight@0: if (wo->train_id[j] == overriding_engine) darkvater@369: return &wo->group; truelight@0: } truelight@0: } truelight@0: return NULL; truelight@0: } truelight@0: truelight@0: Darkvater@1474: byte _engine_original_sprites[TOTAL_NUM_ENGINES]; truelight@0: // 0 - 28 are cargos, 29 is default, 30 is the advert (purchase list) truelight@0: // (It isn't and shouldn't be like this in the GRF files since new cargo types truelight@0: // may appear in future - however it's more convenient to store it like this in truelight@0: // memory. --pasky) tron@1477: static SpriteGroup _engine_custom_sprites[TOTAL_NUM_ENGINES][NUM_CID]; truelight@0: tron@1477: void SetCustomEngineSprites(byte engine, byte cargo, SpriteGroup *group) truelight@0: { darkvater@408: /* FIXME: If we are replacing an override, release original SpriteGroup darkvater@408: * to prevent leaks. But first we need to refcount the SpriteGroup. darkvater@408: * --pasky */ darkvater@369: _engine_custom_sprites[engine][cargo] = *group; truelight@0: } truelight@0: tron@1477: typedef RealSpriteGroup *(*resolve_callback)(SpriteGroup *spritegroup, tron@1477: const Vehicle *veh, void *callback); /* XXX data pointer used as function pointer */ tron@445: tron@1477: static RealSpriteGroup* ResolveVehicleSpriteGroup(SpriteGroup *spritegroup, tron@1477: const Vehicle *veh, resolve_callback callback) tron@426: { tron@445: //debug("spgt %d", spritegroup->type); tron@426: switch (spritegroup->type) { tron@426: case SGT_REAL: tron@426: return &spritegroup->g.real; tron@426: tron@426: case SGT_DETERMINISTIC: { tron@1477: DeterministicSpriteGroup *dsg = &spritegroup->g.determ; tron@1477: SpriteGroup *target; tron@426: int value = -1; tron@426: tron@433: //debug("[%p] Having fun resolving variable %x", veh, dsg->variable); tron@426: tron@426: if ((dsg->variable >> 6) == 0) { tron@426: /* General property */ tron@426: value = GetDeterministicSpriteValue(dsg->variable); tron@426: } else { tron@433: /* Vehicle-specific property. */ tron@433: tron@433: if (veh == NULL) { tron@433: /* We are in a purchase list of something, tron@433: * and we are checking for something undefined. tron@433: * That means we should get the first target tron@433: * (NOT the default one). */ tron@433: if (dsg->num_ranges > 0) { tron@433: target = &dsg->ranges[0].group; tron@433: } else { tron@433: target = dsg->default_group; tron@433: } tron@445: return callback(target, NULL, callback); tron@433: } tron@433: tron@426: if (dsg->var_scope == VSG_SCOPE_PARENT) { tron@426: /* First engine in the vehicle chain */ tron@433: if (veh->type == VEH_Train) tron@426: veh = GetFirstVehicleInChain(veh); tron@426: } tron@426: tron@426: if (dsg->variable == 0x40) { tron@433: if (veh->type == VEH_Train) { tron@426: Vehicle *u = GetFirstVehicleInChain(veh); tron@426: byte chain_before = 0, chain_after = 0; tron@426: tron@426: while (u != veh) { tron@426: u = u->next; tron@426: chain_before++; tron@426: } tron@426: while (u->next != NULL) { tron@426: u = u->next; tron@426: chain_after++; tron@426: }; tron@426: pasky@1559: value = chain_before | chain_after << 8 pasky@1559: | (chain_before + chain_after) << 16; tron@426: } else { tron@426: value = 1; /* 1 vehicle in the chain */ tron@426: } tron@426: tron@426: } else { tron@426: // TTDPatch runs on little-endian arch; tron@426: // Variable is 0x80 + offset in TTD's vehicle structure tron@426: switch (dsg->variable - 0x80) { tron@555: #define veh_prop(id_, value_) case (id_): value = (value_); break tron@426: veh_prop(0x00, veh->type); tron@426: veh_prop(0x01, veh->subtype); tron@426: veh_prop(0x04, veh->index); tron@426: veh_prop(0x05, veh->index & 0xFF); tron@426: /* XXX? Is THIS right? */ tron@555: veh_prop(0x0A, PackOrder(&veh->current_order)); tron@555: veh_prop(0x0B, PackOrder(&veh->current_order) & 0xff); tron@426: veh_prop(0x0C, veh->num_orders); tron@426: veh_prop(0x0D, veh->cur_order_index); tron@426: veh_prop(0x10, veh->load_unload_time_rem); tron@426: veh_prop(0x11, veh->load_unload_time_rem & 0xFF); tron@426: veh_prop(0x12, veh->date_of_last_service); tron@426: veh_prop(0x13, veh->date_of_last_service & 0xFF); tron@426: veh_prop(0x14, veh->service_interval); tron@426: veh_prop(0x15, veh->service_interval & 0xFF); tron@426: veh_prop(0x16, veh->last_station_visited); tron@426: veh_prop(0x17, veh->tick_counter); tron@426: veh_prop(0x18, veh->max_speed); tron@426: veh_prop(0x19, veh->max_speed & 0xFF); tron@426: veh_prop(0x1F, veh->direction); tron@426: veh_prop(0x28, veh->cur_image); tron@426: veh_prop(0x29, veh->cur_image & 0xFF); tron@426: veh_prop(0x32, veh->vehstatus); tron@426: veh_prop(0x33, veh->vehstatus); tron@426: veh_prop(0x34, veh->cur_speed); tron@426: veh_prop(0x35, veh->cur_speed & 0xFF); tron@426: veh_prop(0x36, veh->subspeed); tron@426: veh_prop(0x37, veh->acceleration); tron@426: veh_prop(0x39, veh->cargo_type); tron@426: veh_prop(0x3A, veh->cargo_cap); tron@426: veh_prop(0x3B, veh->cargo_cap & 0xFF); tron@426: veh_prop(0x3C, veh->cargo_count); tron@426: veh_prop(0x3D, veh->cargo_count & 0xFF); tron@426: veh_prop(0x3E, veh->cargo_source); // Probably useless; so what tron@426: veh_prop(0x3F, veh->cargo_days); tron@426: veh_prop(0x40, veh->age); tron@426: veh_prop(0x41, veh->age & 0xFF); tron@426: veh_prop(0x42, veh->max_age); tron@426: veh_prop(0x43, veh->max_age & 0xFF); tron@426: veh_prop(0x44, veh->build_year); tron@426: veh_prop(0x45, veh->unitnumber); tron@426: veh_prop(0x46, veh->engine_type); tron@426: veh_prop(0x47, veh->engine_type & 0xFF); tron@426: veh_prop(0x48, veh->spritenum); tron@426: veh_prop(0x49, veh->day_counter); tron@426: veh_prop(0x4A, veh->breakdowns_since_last_service); tron@426: veh_prop(0x4B, veh->breakdown_ctr); tron@426: veh_prop(0x4C, veh->breakdown_delay); tron@426: veh_prop(0x4D, veh->breakdown_chance); tron@426: veh_prop(0x4E, veh->reliability); tron@426: veh_prop(0x4F, veh->reliability & 0xFF); tron@426: veh_prop(0x50, veh->reliability_spd_dec); tron@426: veh_prop(0x51, veh->reliability_spd_dec & 0xFF); tron@426: veh_prop(0x52, veh->profit_this_year); tron@426: veh_prop(0x53, veh->profit_this_year & 0xFFFFFF); tron@426: veh_prop(0x54, veh->profit_this_year & 0xFFFF); tron@426: veh_prop(0x55, veh->profit_this_year & 0xFF); tron@426: veh_prop(0x56, veh->profit_last_year); tron@426: veh_prop(0x57, veh->profit_last_year & 0xFF); tron@426: veh_prop(0x58, veh->profit_last_year); tron@426: veh_prop(0x59, veh->profit_last_year & 0xFF); truelight@938: /* veh_prop(0x5A, veh->next_in_chain_old); truelight@938: veh_prop(0x5B, veh->next_in_chain_old & 0xFF);*/ tron@426: veh_prop(0x5C, veh->value); tron@426: veh_prop(0x5D, veh->value & 0xFFFFFF); tron@426: veh_prop(0x5E, veh->value & 0xFFFF); tron@426: veh_prop(0x5F, veh->value & 0xFF); tron@426: veh_prop(0x60, veh->string_id); tron@426: veh_prop(0x61, veh->string_id & 0xFF); tron@426: /* 00h..07h=sub image? 40h=in tunnel; actually some kind of status tron@426: * aircraft: >=13h when in flight tron@426: * train, ship: 80h=in depot tron@426: * rv: 0feh=in depot */ tron@426: /* TODO veh_prop(0x62, veh->???); */ tron@426: tron@426: /* TODO: The rest is per-vehicle, I hope no GRF file looks so far. tron@426: * But they won't let us have an easy ride so surely *some* GRF tron@426: * file does. So someone needs to do this too. --pasky */ tron@426: tron@426: #undef veh_prop tron@426: } tron@426: } tron@426: } tron@426: tron@426: target = value != -1 ? EvalDeterministicSpriteGroup(dsg, value) : dsg->default_group; tron@445: //debug("Resolved variable %x: %d, %p", dsg->variable, value, callback); tron@445: return callback(target, veh, callback); tron@445: } tron@445: tron@445: case SGT_RANDOMIZED: { tron@1477: RandomizedSpriteGroup *rsg = &spritegroup->g.random; tron@445: tron@445: if (veh == NULL) { tron@445: /* Purchase list of something. Show the first one. */ tron@445: assert(rsg->num_groups > 0); tron@445: //debug("going for %p: %d", rsg->groups[0], rsg->groups[0].type); tron@445: return callback(&rsg->groups[0], NULL, callback); tron@445: } tron@445: tron@445: if (rsg->var_scope == VSG_SCOPE_PARENT) { tron@445: /* First engine in the vehicle chain */ tron@445: if (veh->type == VEH_Train) tron@445: veh = GetFirstVehicleInChain(veh); tron@445: } tron@445: tron@445: return callback(EvalRandomizedSpriteGroup(rsg, veh->random_bits), veh, callback); tron@426: } tron@426: tron@426: default: tron@445: error("I don't know how to handle such a spritegroup %d!", spritegroup->type); tron@426: return NULL; tron@426: } tron@426: } tron@426: tron@1477: static SpriteGroup *GetVehicleSpriteGroup(byte engine, const Vehicle *v) truelight@0: { tron@1477: SpriteGroup *group; darkvater@414: byte cargo = CID_PURCHASE; truelight@0: darkvater@414: if (v != NULL) { darkvater@414: cargo = _global_cargo_id[_opt.landscape][v->cargo_type]; darkvater@414: } darkvater@414: darkvater@414: group = &_engine_custom_sprites[engine][cargo]; darkvater@414: pasky@1560: if (v != NULL && v->type == VEH_Train) { pasky@1560: SpriteGroup *overset = GetWagonOverrideSpriteSet(engine, v->u.rail.first_engine); truelight@0: tron@1477: if (overset != NULL) group = overset; truelight@0: } truelight@193: tron@445: return group; tron@445: } tron@445: tron@1475: int GetCustomEngineSprite(byte engine, const Vehicle *v, byte direction) tron@445: { tron@1477: SpriteGroup *group; tron@1477: RealSpriteGroup *rsg; tron@445: byte cargo = CID_PURCHASE; tron@445: byte loaded = 0; tron@445: bool in_motion = 0; tron@445: int totalsets, spriteset; tron@445: int r; tron@445: tron@445: if (v != NULL) { tron@445: int capacity = v->cargo_cap; tron@445: tron@445: cargo = _global_cargo_id[_opt.landscape][v->cargo_type]; tron@445: if (capacity == 0) capacity = 1; tron@445: loaded = (v->cargo_count * 100) / capacity; tron@445: in_motion = (v->cur_speed != 0); tron@445: } tron@445: tron@445: group = GetVehicleSpriteGroup(engine, v); tron@445: rsg = ResolveVehicleSpriteGroup(group, v, (resolve_callback) ResolveVehicleSpriteGroup); darkvater@408: tron@426: if (rsg->sprites_per_set == 0 && cargo != 29) { /* XXX magic number */ darkvater@369: // This group is empty but perhaps there'll be a default one. tron@445: rsg = ResolveVehicleSpriteGroup(&_engine_custom_sprites[engine][29], v, tron@445: (resolve_callback) ResolveVehicleSpriteGroup); truelight@0: } truelight@0: darkvater@408: if (!rsg->sprites_per_set) { darkvater@369: // This group is empty. This function users should therefore truelight@0: // look up the sprite number in _engine_original_sprites. truelight@0: return 0; truelight@0: } truelight@0: truelight@0: direction %= 8; darkvater@408: if (rsg->sprites_per_set == 4) truelight@0: direction %= 4; truelight@0: darkvater@408: totalsets = in_motion ? rsg->loaded_count : rsg->loading_count; truelight@0: truelight@0: // My aim here is to make it possible to visually determine absolutely truelight@0: // empty and totally full vehicles. --pasky truelight@0: if (loaded == 100 || totalsets == 1) { // full truelight@0: spriteset = totalsets - 1; truelight@0: } else if (loaded == 0 || totalsets == 2) { // empty truelight@0: spriteset = 0; truelight@0: } else { // something inbetween truelight@0: spriteset = loaded * (totalsets - 2) / 100 + 1; truelight@0: // correct possible rounding errors truelight@0: if (!spriteset) truelight@0: spriteset = 1; truelight@0: else if (spriteset == totalsets - 1) truelight@0: spriteset--; truelight@0: } truelight@0: darkvater@408: r = (in_motion ? rsg->loaded[spriteset] : rsg->loading[spriteset]) + direction; truelight@0: return r; truelight@0: } truelight@0: truelight@0: tron@445: // Global variables are evil, yes, but we would end up with horribly overblown tron@445: // calling convention otherwise and this should be 100% reentrant. tron@445: static byte _vsg_random_triggers; tron@445: static byte _vsg_bits_to_reseed; tron@445: tron@445: extern int _custom_sprites_base; tron@445: tron@1477: static RealSpriteGroup *TriggerVehicleSpriteGroup(SpriteGroup *spritegroup, tron@1477: Vehicle *veh, resolve_callback callback) tron@445: { tron@1477: if (spritegroup->type == SGT_RANDOMIZED) { tron@1477: _vsg_bits_to_reseed |= RandomizedSpriteGroupTriggeredBits( tron@1477: &spritegroup->g.random, tron@1477: _vsg_random_triggers, tron@1477: &veh->waiting_triggers tron@1477: ); tron@1477: } tron@445: tron@445: return ResolveVehicleSpriteGroup(spritegroup, veh, callback); tron@445: } tron@445: tron@1477: static void DoTriggerVehicle(Vehicle *veh, VehicleTrigger trigger, byte base_random_bits, bool first) tron@445: { tron@1477: RealSpriteGroup *rsg; tron@445: byte new_random_bits; tron@445: tron@445: _vsg_random_triggers = trigger; tron@445: _vsg_bits_to_reseed = 0; tron@445: rsg = TriggerVehicleSpriteGroup(GetVehicleSpriteGroup(veh->engine_type, veh), veh, tron@445: (resolve_callback) TriggerVehicleSpriteGroup); tron@445: if (rsg->sprites_per_set == 0 && veh->cargo_type != 29) { /* XXX magic number */ tron@445: // This group turned out to be empty but perhaps there'll be a default one. tron@445: rsg = TriggerVehicleSpriteGroup(&_engine_custom_sprites[veh->engine_type][29], veh, tron@445: (resolve_callback) TriggerVehicleSpriteGroup); tron@445: } truelight@542: new_random_bits = Random(); tron@445: veh->random_bits &= ~_vsg_bits_to_reseed; truelight@542: veh->random_bits |= (first ? new_random_bits : base_random_bits) & _vsg_bits_to_reseed; tron@445: tron@445: switch (trigger) { tron@445: case VEHICLE_TRIGGER_NEW_CARGO: tron@445: /* All vehicles in chain get ANY_NEW_CARGO trigger now. tron@445: * So we call it for the first one and they will recurse. */ tron@445: /* Indexing part of vehicle random bits needs to be tron@445: * same for all triggered vehicles in the chain (to get tron@445: * all the random-cargo wagons carry the same cargo, tron@445: * i.e.), so we give them all the NEW_CARGO triggered tron@445: * vehicle's portion of random bits. */ tron@445: assert(first); tron@445: DoTriggerVehicle(GetFirstVehicleInChain(veh), VEHICLE_TRIGGER_ANY_NEW_CARGO, new_random_bits, false); tron@445: break; tron@445: case VEHICLE_TRIGGER_DEPOT: tron@445: /* We now trigger the next vehicle in chain recursively. tron@445: * The random bits portions may be different for each tron@445: * vehicle in chain. */ tron@445: if (veh->next != NULL) tron@445: DoTriggerVehicle(veh->next, trigger, 0, true); tron@445: break; tron@445: case VEHICLE_TRIGGER_EMPTY: tron@445: /* We now trigger the next vehicle in chain tron@445: * recursively. The random bits portions must be same tron@445: * for each vehicle in chain, so we give them all tron@445: * first chained vehicle's portion of random bits. */ tron@445: if (veh->next != NULL) tron@445: DoTriggerVehicle(veh->next, trigger, first ? new_random_bits : base_random_bits, false); tron@445: break; tron@445: case VEHICLE_TRIGGER_ANY_NEW_CARGO: tron@445: /* Now pass the trigger recursively to the next vehicle tron@445: * in chain. */ tron@445: assert(!first); tron@445: if (veh->next != NULL) tron@445: DoTriggerVehicle(veh->next, VEHICLE_TRIGGER_ANY_NEW_CARGO, base_random_bits, false); tron@445: break; tron@445: } tron@445: } tron@445: tron@1477: void TriggerVehicle(Vehicle *veh, VehicleTrigger trigger) tron@445: { tron@445: DoTriggerVehicle(veh, trigger, 0, true); tron@445: } tron@445: Darkvater@1474: static char *_engine_custom_names[TOTAL_NUM_ENGINES]; truelight@0: tron@1329: void SetCustomEngineName(int engine, const char *name) truelight@0: { truelight@0: _engine_custom_names[engine] = strdup(name); truelight@0: } truelight@0: Darkvater@1474: void UnInitNewgrEngines(void) Darkvater@1474: { Darkvater@1474: char **i; Darkvater@1474: for (i = _engine_custom_names; i != endof(_engine_custom_names); i++) { Darkvater@1474: free(*i); Darkvater@1474: *i = NULL; Darkvater@1474: } Darkvater@1474: } Darkvater@1474: truelight@0: StringID GetCustomEngineName(int engine) truelight@0: { truelight@0: if (!_engine_custom_names[engine]) truelight@0: return _engine_name_strings[engine]; pasky@485: strncpy(_userstring, _engine_custom_names[engine], USERSTRING_LEN); pasky@485: _userstring[USERSTRING_LEN - 1] = '\0'; truelight@0: return STR_SPEC_USERSTRING; truelight@0: } truelight@0: truelight@0: truelight@0: void AcceptEnginePreview(Engine *e, int player) truelight@0: { truelight@0: Player *p; truelight@0: truelight@0: SETBIT(e->player_avail, player); truelight@0: truelight@0: p = DEREF_PLAYER(player); truelight@193: truelight@0: UPDATE_PLAYER_RAILTYPE(e,p); truelight@0: truelight@0: e->preview_player = 0xFF; truelight@0: InvalidateWindowClasses(WC_BUILD_VEHICLE); bjarni@1096: InvalidateWindowClasses(WC_REPLACE_VEHICLE); truelight@0: } truelight@0: tron@1093: void EnginesDailyLoop(void) truelight@0: { truelight@0: Engine *e; truelight@0: int i,num; truelight@0: Player *p; truelight@0: uint mask; truelight@0: int32 best_hist; truelight@0: int best_player; truelight@0: truelight@0: if (_cur_year >= 130) truelight@0: return; truelight@0: truelight@0: for(e=_engines,i=0; i!=TOTAL_NUM_ENGINES; e++,i++) { truelight@0: if (e->flags & ENGINE_INTRODUCING) { truelight@0: if (e->flags & ENGINE_PREVIEWING) { truelight@740: if (e->preview_player != 0xFF && !--e->preview_wait) { truelight@0: e->flags &= ~ENGINE_PREVIEWING; truelight@0: DeleteWindowById(WC_ENGINE_PREVIEW, i); truelight@0: e->preview_player++; truelight@193: } truelight@0: } else if (e->preview_player != 0xFF) { truelight@0: num = e->preview_player; truelight@0: mask = 0; truelight@0: do { truelight@0: best_hist = -1; truelight@0: best_player = -1; truelight@0: FOR_ALL_PLAYERS(p) { truelight@193: if (p->is_active && p->block_preview == 0 && !HASBIT(mask,p->index) && truelight@0: p->old_economy[0].performance_history > best_hist) { truelight@0: best_hist = p->old_economy[0].performance_history; truelight@0: best_player = p->index; truelight@0: } truelight@0: } truelight@0: if (best_player == -1) { truelight@0: e->preview_player = 0xFF; truelight@0: goto next_engine; truelight@0: } truelight@0: mask |= (1 << best_player); truelight@0: } while (--num != 0); truelight@193: truelight@0: if (!IS_HUMAN_PLAYER(best_player)) { truelight@0: /* TTDBUG: TTD has a bug here */ truelight@0: AcceptEnginePreview(e, best_player); truelight@0: } else { truelight@0: e->flags |= ENGINE_PREVIEWING; truelight@0: e->preview_wait = 20; truelight@0: if (IS_INTERACTIVE_PLAYER(best_player)) { truelight@193: ShowEnginePreviewWindow(i); truelight@0: } truelight@0: } truelight@0: } truelight@0: } truelight@0: next_engine:; truelight@0: } truelight@0: } truelight@0: truelight@0: int32 CmdWantEnginePreview(int x, int y, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@0: if (flags & DC_EXEC) { truelight@0: AcceptEnginePreview(&_engines[p1], _current_player); truelight@0: } truelight@0: return 0; truelight@0: } truelight@0: dominik@257: // Determine if an engine type is a wagon (and not a loco) tron@964: static bool IsWagon(byte index) dominik@257: { tron@964: return index < NUM_TRAIN_ENGINES && RailVehInfo(index)->flags & RVI_WAGON; dominik@257: } dominik@257: tron@410: static void NewVehicleAvailable(Engine *e) truelight@0: { truelight@0: Vehicle *v; truelight@0: Player *p; truelight@0: int index = e - _engines; truelight@0: truelight@0: // In case the player didn't build the vehicle during the intro period, truelight@0: // prevent that player from getting future intro periods for a while. truelight@0: if (e->flags&ENGINE_INTRODUCING) { truelight@0: FOR_ALL_PLAYERS(p) { truelight@919: uint block_preview = p->block_preview; truelight@919: truelight@0: if (!HASBIT(e->player_avail,p->index)) truelight@0: continue; truelight@193: truelight@919: /* We assume the user did NOT build it.. prove me wrong ;) */ truelight@919: p->block_preview = 20; truelight@919: truelight@919: FOR_ALL_VEHICLES(v) { truelight@0: if (v->type == VEH_Train || v->type == VEH_Road || v->type == VEH_Ship || truelight@0: (v->type == VEH_Aircraft && v->subtype <= 2)) { truelight@919: if (v->owner == p->index && v->engine_type == index) { truelight@919: /* The user did prove me wrong, so restore old value */ truelight@919: p->block_preview = block_preview; truelight@919: break; truelight@919: } truelight@0: } truelight@0: } truelight@0: } truelight@0: } truelight@0: dominik@114: e->flags = (e->flags & ~ENGINE_INTRODUCING) | ENGINE_AVAILABLE; dominik@114: InvalidateWindowClasses(WC_BUILD_VEHICLE); bjarni@1096: InvalidateWindowClasses(WC_REPLACE_VEHICLE); dominik@114: truelight@0: // Now available for all players truelight@0: e->player_avail = (byte)-1; dominik@114: dominik@114: // Do not introduce new rail wagons tron@964: if (IsWagon(index)) dominik@257: return; dominik@114: dominik@114: // make maglev / monorail available truelight@0: FOR_ALL_PLAYERS(p) { truelight@0: if (p->is_active) truelight@0: UPDATE_PLAYER_RAILTYPE(e,p); truelight@0: } truelight@0: truelight@0: if ((byte)index < NUM_TRAIN_ENGINES) { truelight@0: AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_TRAINAVAIL), 0, 0); truelight@0: } else if ((byte)index < NUM_TRAIN_ENGINES + NUM_ROAD_ENGINES) { truelight@0: AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_ROADAVAIL), 0, 0); truelight@0: } else if ((byte)index < NUM_TRAIN_ENGINES + NUM_ROAD_ENGINES + NUM_SHIP_ENGINES) { truelight@0: AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_SHIPAVAIL), 0, 0); truelight@0: } else { truelight@0: AddNewsItem(index, NEWS_FLAGS(NM_CALLBACK, 0, NT_NEW_VEHICLES, DNC_AIRCRAFTAVAIL), 0, 0); truelight@0: } truelight@0: } truelight@0: tron@1093: void EnginesMonthlyLoop(void) truelight@0: { truelight@0: Engine *e; truelight@0: truelight@0: if (_cur_year < 130) { truelight@0: for(e=_engines; e != endof(_engines); e++) { truelight@0: // Age the vehicle truelight@0: if (e->flags&ENGINE_AVAILABLE && e->age != 0xFFFF) { truelight@0: e->age++; truelight@0: CalcEngineReliability(e); truelight@0: } truelight@0: truelight@0: if (!(e->flags & ENGINE_AVAILABLE) && (uint16)(_date - min(_date, 365)) >= e->intro_date) { truelight@0: // Introduce it to all players truelight@0: NewVehicleAvailable(e); truelight@0: } else if (!(e->flags & (ENGINE_AVAILABLE|ENGINE_INTRODUCING)) && _date >= e->intro_date) { truelight@0: // Introduction date has passed.. show introducing dialog to one player. truelight@0: e->flags |= ENGINE_INTRODUCING; dominik@257: dominik@257: // Do not introduce new rail wagons tron@964: if (!IsWagon(e - _engines)) dominik@257: e->preview_player = 1; // Give to the player with the highest rating. truelight@0: } truelight@0: } truelight@0: } truelight@0: AdjustAvailAircraft(); truelight@0: } truelight@0: truelight@0: int32 CmdRenameEngine(int x, int y, uint32 flags, uint32 p1, uint32 p2) truelight@0: { truelight@0: StringID str; truelight@0: tron@1328: str = AllocateNameUnique((const char*)_decode_parameters, 0); truelight@0: if (str == 0) truelight@0: return CMD_ERROR; truelight@193: truelight@0: if (flags & DC_EXEC) { truelight@0: StringID old_str = _engine_name_strings[p1]; truelight@0: _engine_name_strings[p1] = str; truelight@0: DeleteName(old_str); truelight@0: _vehicle_design_names |= 3; truelight@0: MarkWholeScreenDirty(); truelight@0: } else { truelight@0: DeleteName(str); truelight@0: } truelight@0: truelight@0: return 0; truelight@0: } truelight@0: truelight@0: int GetPlayerMaxRailtype(int p) truelight@0: { truelight@0: Engine *e; truelight@0: int rt = 0; truelight@0: int i; truelight@0: truelight@0: for(e=_engines,i=0; i!=lengthof(_engines); e++,i++) { truelight@0: if (!HASBIT(e->player_avail, p)) truelight@0: continue; truelight@0: truelight@0: if ((i >= 27 && i < 54) || (i >= 57 && i < 84) || (i >= 89 && i < 116)) truelight@0: continue; truelight@0: truelight@0: if (rt < e->railtype) truelight@0: rt = e->railtype; truelight@0: } truelight@0: truelight@0: return rt + 1; truelight@0: } truelight@0: truelight@0: truelight@0: static const byte _engine_desc[] = { truelight@0: SLE_VAR(Engine,intro_date, SLE_UINT16), truelight@0: SLE_VAR(Engine,age, SLE_UINT16), truelight@0: SLE_VAR(Engine,reliability, SLE_UINT16), truelight@0: SLE_VAR(Engine,reliability_spd_dec, SLE_UINT16), truelight@0: SLE_VAR(Engine,reliability_start, SLE_UINT16), truelight@0: SLE_VAR(Engine,reliability_max, SLE_UINT16), truelight@0: SLE_VAR(Engine,reliability_final, SLE_UINT16), truelight@0: SLE_VAR(Engine,duration_phase_1, SLE_UINT16), truelight@0: SLE_VAR(Engine,duration_phase_2, SLE_UINT16), truelight@0: SLE_VAR(Engine,duration_phase_3, SLE_UINT16), truelight@0: truelight@0: SLE_VAR(Engine,lifelength, SLE_UINT8), truelight@0: SLE_VAR(Engine,flags, SLE_UINT8), truelight@0: SLE_VAR(Engine,preview_player, SLE_UINT8), truelight@0: SLE_VAR(Engine,preview_wait, SLE_UINT8), truelight@0: SLE_VAR(Engine,railtype, SLE_UINT8), truelight@0: SLE_VAR(Engine,player_avail, SLE_UINT8), truelight@0: truelight@0: // reserve extra space in savegame here. (currently 16 bytes) truelight@0: SLE_CONDARR(NullStruct,null,SLE_FILE_U64 | SLE_VAR_NULL, 2, 2, 255), truelight@0: truelight@0: SLE_END() truelight@0: }; truelight@0: tron@1093: static void Save_ENGN(void) truelight@0: { truelight@0: Engine *e; truelight@0: int i; truelight@0: for(i=0,e=_engines; i != lengthof(_engines); i++,e++) { truelight@0: SlSetArrayIndex(i); truelight@0: SlObject(e, _engine_desc); truelight@0: } truelight@0: } truelight@0: tron@1093: static void Load_ENGN(void) truelight@0: { truelight@0: int index; truelight@0: while ((index = SlIterateArray()) != -1) { truelight@0: SlObject(&_engines[index], _engine_desc); truelight@0: } truelight@0: } truelight@0: tron@1093: static void LoadSave_ENGS(void) truelight@0: { truelight@0: SlArray(_engine_name_strings, lengthof(_engine_name_strings), SLE_STRINGID); truelight@0: } truelight@0: truelight@0: const ChunkHandler _engine_chunk_handlers[] = { truelight@0: { 'ENGN', Save_ENGN, Load_ENGN, CH_ARRAY}, truelight@0: { 'ENGS', LoadSave_ENGS, LoadSave_ENGS, CH_RIFF | CH_LAST}, truelight@0: }; truelight@0: tron@1197: bjarni@1196: /* tron@1197: * returns true if an engine is valid, of the specified type, and buildable by tron@1197: * the current player, false otherwise bjarni@1196: * bjarni@1196: * engine = index of the engine to check bjarni@1196: * type = the type the engine should be of (VEH_xxx) bjarni@1196: */ tron@1197: bool IsEngineBuildable(uint engine, byte type) tron@1197: { tron@1197: const Engine *e; bjarni@1196: bjarni@1196: // check if it's an engine that is in the engine array tron@1197: if (engine >= TOTAL_NUM_ENGINES) return false; bjarni@1196: bjarni@1196: e = DEREF_ENGINE(engine); bjarni@1196: bjarni@1196: // check if it's an engine of specified type bjarni@1196: if (e->type != type) return false; bjarni@1196: bjarni@1196: // check if it's available bjarni@1196: if (!HASBIT(e->player_avail, _current_player)) return false; bjarni@1196: bjarni@1196: return true; bjarni@1196: }