diff --git a/src/engine_base.h b/src/engine_base.h index de26c8a06c..3eabf67382 100644 --- a/src/engine_base.h +++ b/src/engine_base.h @@ -16,6 +16,8 @@ #include "core/tinystring_type.hpp" #include "newgrf_commons.h" +#include "3rdparty/cpp-btree/btree_map.h" + typedef Pool EnginePool; extern EnginePool _engine_pool; @@ -61,6 +63,10 @@ struct Engine : EnginePool::PoolItem<&_engine_pool> { struct WagonOverride *overrides; uint16 list_position; + SpriteGroupCallbacksUsed callbacks_used = SGCU_ALL; + uint64 cb36_properties_used = UINT64_MAX; + btree::btree_map sprite_group_cb36_properties_used; + Engine(); Engine(VehicleType type, EngineID base); ~Engine(); diff --git a/src/engine_func.h b/src/engine_func.h index b046490c1f..b778956171 100644 --- a/src/engine_func.h +++ b/src/engine_func.h @@ -17,6 +17,7 @@ void SetupEngines(); void StartupEngines(); void CheckEngines(); +void AnalyseEngineCallbacks(); /* Original engine data counts and offsets */ extern const uint8 _engine_counts[4]; diff --git a/src/newgrf_commons.h b/src/newgrf_commons.h index ed18aaa9e2..7ffb05e11c 100644 --- a/src/newgrf_commons.h +++ b/src/newgrf_commons.h @@ -333,4 +333,12 @@ struct GRFFileProps : GRFFilePropsBase<1> { uint16 override; ///< id of the entity been replaced by }; +enum SpriteGroupCallbacksUsed : uint8 { + SGCU_NONE = 0, + SGCU_ALL = 0xFF, + SGCU_VEHICLE_32DAY_CALLBACK = 1 << 0, + SGCU_VEHICLE_REFIT_COST = 1 << 1, +}; +DECLARE_ENUM_AS_BIT_SET(SpriteGroupCallbacksUsed) + #endif /* NEWGRF_COMMONS_H */ diff --git a/src/newgrf_engine.cpp b/src/newgrf_engine.cpp index da1ed5261e..ce49977563 100644 --- a/src/newgrf_engine.cpp +++ b/src/newgrf_engine.cpp @@ -24,6 +24,7 @@ #include "newgrf_roadtype.h" #include "newgrf_cache_check.h" #include "ship.h" +#include "scope_info.h" #include "safeguards.h" @@ -1355,7 +1356,17 @@ uint GetVehicleProperty(const Vehicle *v, PropertyID property, uint orig_value) uint GetEngineProperty(EngineID engine, PropertyID property, uint orig_value, const Vehicle *v) { - uint16 callback = GetVehicleCallback(CBID_VEHICLE_MODIFY_PROPERTY, property, 0, engine, v); + const Engine *e = Engine::Get(engine); + if (property < 64 && !HasBit(e->cb36_properties_used, property)) return orig_value; + + VehicleResolverObject object(engine, v, VehicleResolverObject::WO_UNCACHED, false, CBID_VEHICLE_MODIFY_PROPERTY, property, 0); + if (property < 64 && !e->sprite_group_cb36_properties_used.empty()) { + auto iter = e->sprite_group_cb36_properties_used.find(object.root_spritegroup); + if (iter != e->sprite_group_cb36_properties_used.end()) { + if (!HasBit(iter->second, property)) return orig_value; + } + } + uint16 callback = object.ResolveCallback(); if (callback != CALLBACK_FAILED) return callback; return orig_value; @@ -1562,3 +1573,39 @@ void FillNewGRFVehicleCache(const Vehicle *v) /* Make sure really all bits are set. */ assert(v->grf_cache.cache_valid == (1 << NCVV_END) - 1); } + +void AnalyseEngineCallbacks() +{ + btree::btree_map sg_cb36; + for (Engine *e : Engine::Iterate()) { + sg_cb36.clear(); + e->sprite_group_cb36_properties_used.clear(); + + SpriteGroupCallbacksUsed callbacks_used = SGCU_NONE; + uint64 cb36_properties_used = 0; + auto process_sg = [&](const SpriteGroup *sg) { + if (sg == nullptr) return; + + AnalyseCallbackOperation op; + sg->AnalyseCallbacks(op); + callbacks_used |= op.callbacks_used; + cb36_properties_used |= op.properties_used; + sg_cb36[sg] = op.properties_used; + }; + + AnalyseCallbackOperation op; + for (uint i = 0; i < NUM_CARGO + 2; i++) { + process_sg(e->grf_prop.spritegroup[i]); + } + for (uint i = 0; i < e->overrides_count; i++) { + process_sg(e->overrides[i].group); + } + e->callbacks_used = callbacks_used; + e->cb36_properties_used = cb36_properties_used; + for (auto iter : sg_cb36) { + if (iter.second != cb36_properties_used) { + e->sprite_group_cb36_properties_used[iter.first] = iter.second; + } + } + } +} diff --git a/src/newgrf_spritegroup.cpp b/src/newgrf_spritegroup.cpp index 4ce93e1459..f26b30062c 100644 --- a/src/newgrf_spritegroup.cpp +++ b/src/newgrf_spritegroup.cpp @@ -256,6 +256,162 @@ const SpriteGroup *DeterministicSpriteGroup::Resolve(ResolverObject &object) con return SpriteGroup::Resolve(this->default_group, object, false); } +void DeterministicSpriteGroup::AnalyseCallbacks(AnalyseCallbackOperation &op) const +{ + auto res = op.seen.insert(this); + if (!res.second) { + /* Already seen this group */ + return; + } + + auto check_1A_range = [&]() -> bool { + if (this->adjusts.size() == 1 && this->adjusts[0].variable == 0x1A) { + /* Not clear why some GRFs do this, perhaps a way of commenting out a branch */ + uint32 value = 0; + switch (this->size) { + case DSG_SIZE_BYTE: value = EvalAdjustT (this->adjusts[0], nullptr, 0, UINT_MAX); break; + case DSG_SIZE_WORD: value = EvalAdjustT(this->adjusts[0], nullptr, 0, UINT_MAX); break; + case DSG_SIZE_DWORD: value = EvalAdjustT(this->adjusts[0], nullptr, 0, UINT_MAX); break; + default: NOT_REACHED(); + } + for (const auto &range : this->ranges) { + if (range.low <= value && value <= range.high) { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + return true; + } + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + return true; + } + return false; + }; + + if (op.mode == ACOM_FIND_CB_RESULT) { + if (this->calculated_result) { + op.cb_result_found = true; + return; + } else if (!op.cb_result_found) { + if (check_1A_range()) return; + if (this->adjusts.size() == 1 && this->adjusts[0].variable == 0xC) { + const auto &adjust = this->adjusts[0]; + if (adjust.shift_num == 0 && (adjust.and_mask & 0xFF) == 0xFF && adjust.type == DSGA_TYPE_NONE) { + for (const auto &range : this->ranges) { + if (range.low == range.high && range.low == 0xC) { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + return; + } + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + return; + } + } + for (const auto &range : this->ranges) { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + } + return; + } + + if (check_1A_range()) return; + + auto find_cb_result = [&]() -> bool { + if (this->calculated_result) return true; + AnalyseCallbackOperation cbr_op; + cbr_op.mode = ACOM_FIND_CB_RESULT; + for (const auto &range : this->ranges) { + if (range.group != nullptr) range.group->AnalyseCallbacks(cbr_op); + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(cbr_op); + return cbr_op.cb_result_found; + }; + + if (this->adjusts.size() == 1 && !this->calculated_result) { + const auto &adjust = this->adjusts[0]; + if (op.mode == ACOM_CB_VAR && adjust.variable == 0xC) { + if (adjust.shift_num == 0 && (adjust.and_mask & 0xFF) == 0xFF && adjust.type == DSGA_TYPE_NONE) { + for (const auto &range : this->ranges) { + if (range.low == range.high) { + switch (range.low) { + case CBID_VEHICLE_32DAY_CALLBACK: + op.callbacks_used |= SGCU_VEHICLE_32DAY_CALLBACK; + break; + + case CBID_VEHICLE_REFIT_COST: + op.callbacks_used |= SGCU_VEHICLE_REFIT_COST; + break; + + case CBID_VEHICLE_MODIFY_PROPERTY: + if (range.group != nullptr) { + AnalyseCallbackOperation cb36_op; + cb36_op.mode = ACOM_CB36_PROP; + range.group->AnalyseCallbacks(cb36_op); + if (cb36_op.properties_used == UINT64_MAX) DumpSpriteGroup(range.group, 0); + op.properties_used |= cb36_op.properties_used; + } + break; + } + } else { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + } + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + return; + } + } + if (op.mode == ACOM_CB36_PROP && adjust.variable == 0x10) { + if (adjust.shift_num == 0 && (adjust.and_mask & 0xFF) == 0xFF && adjust.type == DSGA_TYPE_NONE) { + for (const auto &range : this->ranges) { + if (range.low == range.high) { + if (range.low < 64) { + if (find_cb_result()) SetBit(op.properties_used, range.low); + } + } else { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + } + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + return; + } + } + if (op.mode == ACOM_CB36_PROP && adjust.variable == 0xC) { + if (adjust.shift_num == 0 && (adjust.and_mask & 0xFF) == 0xFF && adjust.type == DSGA_TYPE_NONE) { + for (const auto &range : this->ranges) { + if (range.low <= CBID_VEHICLE_MODIFY_PROPERTY && CBID_VEHICLE_MODIFY_PROPERTY <= range.high) { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + return; + } + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + return; + } + } + } + for (const auto &adjust : this->adjusts) { + if (op.mode == ACOM_CB_VAR && adjust.variable == 0xC) { + op.callbacks_used |= SGCU_ALL; + } + if (op.mode == ACOM_CB36_PROP && adjust.variable == 0x10) { + if (find_cb_result()) { + op.properties_used |= UINT64_MAX; + } + } + if (adjust.variable == 0x7E && adjust.subroutine != nullptr) { + adjust.subroutine->AnalyseCallbacks(op); + } + } + if (!this->calculated_result) { + for (const auto &range : this->ranges) { + if (range.group != nullptr) range.group->AnalyseCallbacks(op); + } + if (this->default_group != nullptr) this->default_group->AnalyseCallbacks(op); + } +} + +void CallbackResultSpriteGroup::AnalyseCallbacks(AnalyseCallbackOperation &op) const +{ + if (op.mode == ACOM_FIND_CB_RESULT) op.cb_result_found = true; +} const SpriteGroup *RandomizedSpriteGroup::Resolve(ResolverObject &object) const { diff --git a/src/newgrf_spritegroup.h b/src/newgrf_spritegroup.h index e49b263ff5..edd3df0f0e 100644 --- a/src/newgrf_spritegroup.h +++ b/src/newgrf_spritegroup.h @@ -20,6 +20,8 @@ #include "newgrf_storage.h" #include "newgrf_commons.h" +#include "3rdparty/cpp-btree/btree_set.h" + /** * Gets the value of a so-called newgrf "register". * @param i index of the register @@ -47,6 +49,20 @@ struct SpriteGroup; typedef uint32 SpriteGroupID; struct ResolverObject; +enum AnalyseCallbackOperationMode { + ACOM_CB_VAR, + ACOM_CB36_PROP, + ACOM_FIND_CB_RESULT, +}; + +struct AnalyseCallbackOperation { + btree::btree_set seen; + AnalyseCallbackOperationMode mode = ACOM_CB_VAR; + SpriteGroupCallbacksUsed callbacks_used = SGCU_NONE; + uint64 properties_used = 0; + bool cb_result_found = false; +}; + /* SPRITE_WIDTH is 24. ECS has roughly 30 sprite groups per real sprite. * Adding an 'extra' margin would be assuming 64 sprite groups per real * sprite. 64 = 2^6, so 2^30 should be enough (for now) */ @@ -69,6 +85,7 @@ public: virtual SpriteID GetResult() const { return 0; } virtual byte GetNumResults() const { return 0; } virtual uint16 GetCallbackResult() const { return CALLBACK_FAILED; } + virtual void AnalyseCallbacks(AnalyseCallbackOperation &op) const {}; static const SpriteGroup *Resolve(const SpriteGroup *group, ResolverObject &object, bool top_level = true); }; @@ -178,6 +195,8 @@ struct DeterministicSpriteGroup : SpriteGroup { const SpriteGroup *error_group; // was first range, before sorting ranges + void AnalyseCallbacks(AnalyseCallbackOperation &op) const override; + protected: const SpriteGroup *Resolve(ResolverObject &object) const; }; @@ -228,6 +247,7 @@ struct CallbackResultSpriteGroup : SpriteGroup { uint16 result; uint16 GetCallbackResult() const { return this->result; } + void AnalyseCallbacks(AnalyseCallbackOperation &op) const override; }; diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp index 7a1d8e29b1..89cc76bed3 100644 --- a/src/saveload/afterload.cpp +++ b/src/saveload/afterload.cpp @@ -938,6 +938,8 @@ bool AfterLoadGame() _settings_game.vehicle.train_braking_model = TBM_ORIGINAL; } + AfterLoadEngines(); + /* Update all vehicles */ AfterLoadVehicles(true); @@ -4012,6 +4014,7 @@ void ReloadNewGRFData() RecomputePrices(); /* reload vehicles */ ResetVehicleHash(); + AfterLoadEngines(); AfterLoadVehicles(false); StartupEngines(); GroupStatistics::UpdateAfterLoad(); diff --git a/src/saveload/engine_sl.cpp b/src/saveload/engine_sl.cpp index 78f67f9e20..77635f6a1c 100644 --- a/src/saveload/engine_sl.cpp +++ b/src/saveload/engine_sl.cpp @@ -10,6 +10,7 @@ #include "../stdafx.h" #include "saveload_internal.h" #include "../engine_base.h" +#include "../engine_func.h" #include "../string_func.h" #include @@ -196,6 +197,11 @@ static void Load_EIDS() } } +void AfterLoadEngines() +{ + AnalyseEngineCallbacks(); +} + extern const ChunkHandler _engine_chunk_handlers[] = { { 'EIDS', Save_EIDS, Load_EIDS, nullptr, nullptr, CH_ARRAY }, { 'ENGN', Save_ENGN, Load_ENGN, nullptr, nullptr, CH_ARRAY }, diff --git a/src/saveload/saveload_internal.h b/src/saveload/saveload_internal.h index 1f7b2e7511..11bfd00169 100644 --- a/src/saveload/saveload_internal.h +++ b/src/saveload/saveload_internal.h @@ -26,6 +26,7 @@ void MoveWaypointsToBaseStations(); const SaveLoad *GetBaseStationDescription(); void AfterLoadVehicles(bool part_of_load); +void AfterLoadEngines(); void FixupTrainLengths(); void AfterLoadTemplateVehicles(); void AfterLoadStations(); diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index 6a07c1123e..d352c3abe9 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -10,6 +10,7 @@ #include "../newgrf_house.h" #include "../newgrf_engine.h" #include "../newgrf_roadtype.h" +#include "../newgrf_cargo.h" #include "../date_func.h" #include "../timetable.h" #include "../ship.h" @@ -272,6 +273,39 @@ class NIHVehicle : public NIHelper { print(buffer); const Engine *e = Engine::GetIfValid(v->engine_type); if (e != nullptr) { + seprintf(buffer, lastof(buffer), " Callbacks: 0x%X, CB36 Properties: 0x" OTTD_PRINTFHEX64, + e->callbacks_used, e->cb36_properties_used); + print(buffer); + uint64 cb36_properties = e->cb36_properties_used; + if (!e->sprite_group_cb36_properties_used.empty()) { + const SpriteGroup *root_spritegroup = nullptr; + if (v->IsGroundVehicle()) root_spritegroup = GetWagonOverrideSpriteSet(v->engine_type, v->cargo_type, v->GetGroundVehicleCache()->first_engine); + if (root_spritegroup == nullptr) { + CargoID cargo = v->cargo_type; + assert(cargo < lengthof(e->grf_prop.spritegroup)); + root_spritegroup = e->grf_prop.spritegroup[cargo] != nullptr ? e->grf_prop.spritegroup[cargo] : e->grf_prop.spritegroup[CT_DEFAULT]; + } + auto iter = e->sprite_group_cb36_properties_used.find(root_spritegroup); + if (iter != e->sprite_group_cb36_properties_used.end()) { + cb36_properties = iter->second; + seprintf(buffer, lastof(buffer), " Current sprite group: CB36 Properties: 0x" OTTD_PRINTFHEX64, iter->second); + print(buffer); + } + } + if (cb36_properties != UINT64_MAX) { + uint64 props = cb36_properties; + while (props) { + PropertyID prop = (PropertyID)FindFirstBit64(props); + props = KillFirstBit(props); + uint16 res = GetVehicleProperty(v, prop, CALLBACK_FAILED); + if (res == CALLBACK_FAILED) { + seprintf(buffer, lastof(buffer), " CB36: 0x%X --> FAILED", prop); + } else { + seprintf(buffer, lastof(buffer), " CB36: 0x%X --> 0x%X", prop, res); + } + print(buffer); + } + } YearMonthDay ymd; ConvertDateToYMD(e->intro_date, &ymd); seprintf(buffer, lastof(buffer), " Intro: %4i-%02i-%02i, Age: %u, Base life: %u, Durations: %u %u %u (sum: %u)", diff --git a/src/vehicle.cpp b/src/vehicle.cpp index c81ff2e61f..17af6f66d0 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1203,7 +1203,7 @@ static void RunVehicleDayProc() if (v == nullptr) continue; /* Call the 32-day callback if needed */ - if ((v->day_counter & 0x1F) == 0 && v->HasEngineType()) { + if ((v->day_counter & 0x1F) == 0 && v->HasEngineType() && (Engine::Get(v->engine_type)->callbacks_used & SGCU_VEHICLE_32DAY_CALLBACK) != 0) { uint16 callback = GetVehicleCallback(CBID_VEHICLE_32DAY_CALLBACK, 0, 0, v->engine_type, v); if (callback != CALLBACK_FAILED) { if (HasBit(callback, 0)) { diff --git a/src/vehicle_cmd.cpp b/src/vehicle_cmd.cpp index eadc547e29..f20a6c5bd9 100644 --- a/src/vehicle_cmd.cpp +++ b/src/vehicle_cmd.cpp @@ -272,7 +272,7 @@ static int GetRefitCostFactor(const Vehicle *v, EngineID engine_type, CargoID ne const Engine *e = Engine::Get(engine_type); /* Is this vehicle a NewGRF vehicle? */ - if (e->GetGRF() != nullptr) { + if (e->GetGRF() != nullptr && (e->callbacks_used & SGCU_VEHICLE_REFIT_COST) != 0) { const CargoSpec *cs = CargoSpec::Get(new_cid); uint32 param1 = (cs->classes << 16) | (new_subtype << 8) | e->GetGRF()->cargo_map[new_cid];