diff --git a/source.list b/source.list index df35cdd26e..c9834dcf31 100644 --- a/source.list +++ b/source.list @@ -367,6 +367,7 @@ tilematrix_type.hpp timetable.h toolbar_gui.h town.h +town_gui.h town_type.h townname_func.h townname_type.h diff --git a/src/command.cpp b/src/command.cpp index 959610cd28..991fa3f2ca 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -50,6 +50,7 @@ CommandProc CmdRemoveSingleSignal; CommandProc CmdTerraformLand; CommandProc CmdBuildObject; +CommandProc CmdBuildHouse; CommandProc CmdSellLandArea; CommandProc CmdBuildTunnel; @@ -220,6 +221,7 @@ static const Command _command_proc_table[] = { DEF_CMD(CmdRemoveSingleSignal, CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_SIGNALS DEF_CMD(CmdTerraformLand, CMD_ALL_TILES | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_TERRAFORM_LAND DEF_CMD(CmdBuildObject, CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_OBJECT + DEF_CMD(CmdBuildHouse, CMD_DEITY | CMD_NO_WATER | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_HOUSE DEF_CMD(CmdBuildTunnel, CMD_DEITY | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_BUILD_TUNNEL DEF_CMD(CmdRemoveFromRailStation, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_REMOVE_FROM_RAIL_STATION DEF_CMD(CmdConvertRail, 0, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_CONVERT_RAILD diff --git a/src/command_type.h b/src/command_type.h index f318216acc..ded1c4aa92 100644 --- a/src/command_type.h +++ b/src/command_type.h @@ -187,6 +187,7 @@ enum Commands { CMD_REMOVE_SIGNALS, ///< remove a signal CMD_TERRAFORM_LAND, ///< terraform a tile CMD_BUILD_OBJECT, ///< build an object + CMD_BUILD_HOUSE, ///< build a house CMD_BUILD_TUNNEL, ///< build a tunnel CMD_REMOVE_FROM_RAIL_STATION, ///< remove a (rectangle of) tiles from a rail station diff --git a/src/house.h b/src/house.h index ddc2a448c6..17aee20c7f 100644 --- a/src/house.h +++ b/src/house.h @@ -143,4 +143,9 @@ static inline HouseID GetTranslatedHouseID(HouseID hid) return hs->grf_prop.override == INVALID_HOUSE_ID ? hid : hs->grf_prop.override; } +StringID GetHouseName(HouseID house, TileIndex tile = INVALID_TILE); +void DrawHouseImage(HouseID house_id, int left, int top, int right, int bottom); +void AddProducedHouseCargo(HouseID house_id, TileIndex tile, CargoArray &produced); +void AddAcceptedHouseCargo(HouseID house_id, TileIndex tile, CargoArray &acceptance, uint32 *always_accepted = NULL); + #endif /* HOUSE_H */ diff --git a/src/lang/english.txt b/src/lang/english.txt index ad29b3d596..63f98a9d13 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -359,6 +359,7 @@ STR_SCENEDIT_TOOLBAR_ROAD_CONSTRUCTION :{BLACK}Road con STR_SCENEDIT_TOOLBAR_PLANT_TREES :{BLACK}Plant trees. Shift toggles building/showing cost estimate STR_SCENEDIT_TOOLBAR_PLACE_SIGN :{BLACK}Place sign STR_SCENEDIT_TOOLBAR_PLACE_OBJECT :{BLACK}Place object. Shift toggles building/showing cost estimate +STR_SCENEDIT_TOOLBAR_PLACE_HOUSE :{BLACK}Place house ############ range for SE file menu starts STR_SCENEDIT_FILE_MENU_SAVE_SCENARIO :Save scenario @@ -2486,6 +2487,36 @@ STR_OBJECT_BUILD_SIZE :{BLACK}Size: {G STR_OBJECT_CLASS_LTHS :Lighthouses STR_OBJECT_CLASS_TRNS :Transmitters +#House construction window (for SE only) +STR_HOUSE_BUILD_CAPTION :{WHITE}House Selection +STR_HOUSE_BUILD_CUSTOM_CAPTION :{WHITE}{RAW_STRING} +STR_HOUSE_BUILD_HOUSESET_LIST_TOOLTIP :{BLACK}Select set of houses +STR_HOUSE_BUILD_SELECT_HOUSE_TOOLTIP :{BLACK}Select house to build +STR_HOUSE_BUILD_HOUSE_NAME :{GOLD}{STRING1} +STR_HOUSE_BUILD_HISTORICAL_BUILDING :{GOLD}(historical building) +STR_HOUSE_BUILD_HOUSE_POPULATION :{BLACK}Population: {GOLD}{NUM} +STR_HOUSE_BUILD_HOUSE_ZONES :{BLACK}House zones: {STRING1} {STRING1} {STRING1} {STRING1} {STRING1} +STR_HOUSE_BUILD_HOUSE_ZONE_DISABLED :{GRAY}{NUM} +STR_HOUSE_BUILD_HOUSE_ZONE_ENABLED :{GOLD}{NUM} +STR_HOUSE_BUILD_LANDSCAPE :{BLACK}Landscape: {STRING} +STR_HOUSE_BUILD_LANDSCAPE_ABOVE_OR_BELOW_SNOWLINE :{GOLD}above or below snowline +STR_HOUSE_BUILD_LANDSCAPE_ONLY_ABOVE_SNOWLINE :{GOLD}only above snowline +STR_HOUSE_BUILD_LANDSCAPE_ONLY_BELOW_SNOWLINE :{GOLD}only below snowline +STR_HOUSE_BUILD_YEARS :{BLACK}Years: {STRING1}{GOLD} - {STRING1} +STR_HOUSE_BUILD_YEARS_BAD_YEAR :{RED}{NUM} +STR_HOUSE_BUILD_YEARS_GOOD_YEAR :{GOLD}{NUM} +STR_HOUSE_BUILD_SUPPLIED_CARGO :{BLACK}Supplies: {GOLD}{CARGO_LIST} +STR_HOUSE_BUILD_ACCEPTED_CARGO :{BLACK}Accepts: {GOLD}{RAW_STRING} +STR_HOUSE_BUILD_CARGO_FIRST :{STRING2} +STR_HOUSE_BUILD_CARGO_SEPARATED :, {STRING2} +STR_HOUSE_BUILD_CARGO_VALUE_JUST_NAME :{1:STRING} +STR_HOUSE_BUILD_CARGO_VALUE_EIGHTS :({COMMA}/8 {STRING}) +STR_BASIC_HOUSE_SET_NAME :Basic houses + +#Town select window (for SE only) +STR_SELECT_TOWN_CAPTION :{WHITE}Select town +STR_SELECT_TOWN_LIST_ITEM :{BLACK}{TOWN} + # Tree planting window (last two for SE only) STR_PLANT_TREE_CAPTION :{WHITE}Trees STR_PLANT_TREE_TOOLTIP :{BLACK}Select tree type to plant. If the tile already has a tree, this will add more trees of mixed types independent of the selected type @@ -4166,6 +4197,7 @@ STR_ERROR_CAN_T_GENERATE_TOWN :{WHITE}Can't bu STR_ERROR_CAN_T_RENAME_TOWN :{WHITE}Can't rename town... STR_ERROR_CAN_T_FOUND_TOWN_HERE :{WHITE}Can't found town here... STR_ERROR_CAN_T_EXPAND_TOWN :{WHITE}Can't expand town... +STR_ERROR_CAN_T_BUILD_HOUSE_HERE :{WHITE}Can't build house here... STR_ERROR_TOO_CLOSE_TO_EDGE_OF_MAP_SUB :{WHITE}... too close to edge of map STR_ERROR_TOO_CLOSE_TO_ANOTHER_TOWN :{WHITE}... too close to another town STR_ERROR_TOO_MANY_TOWNS :{WHITE}... too many towns @@ -4174,6 +4206,15 @@ STR_ERROR_TOWN_EXPAND_WARN_NO_ROADS :{WHITE}The town STR_ERROR_ROAD_WORKS_IN_PROGRESS :{WHITE}Road works in progress STR_ERROR_TOWN_CAN_T_DELETE :{WHITE}Can't delete this town...{}A station or depot is referring to the town or a town owned tile can't be removed STR_ERROR_STATUE_NO_SUITABLE_PLACE :{WHITE}... there is no suitable place for a statue in the centre of this town +STR_ERROR_BUILDING_NOT_ALLOWED_IN_THIS_TOWN_ZONE :{WHITE}... not allowed in this town zone. +STR_ERROR_BUILDING_NOT_ALLOWED_ABOVE_SNOW_LINE :{WHITE}... not allowed above the snow line. +STR_ERROR_BUILDING_NOT_ALLOWED_BELOW_SNOW_LINE :{WHITE}... not allowed below the snow line. +STR_ERROR_TOO_MANY_HOUSE_SETS :{WHITE}... too many house sets in the town +STR_ERROR_TOO_MANY_HOUSE_TYPES :{WHITE}... too many house types in the town +STR_ERROR_BUILDING_IS_TOO_OLD :{WHITE}... building is too old. +STR_ERROR_BUILDING_IS_TOO_MODERN :{WHITE}... building is too modern. +STR_ERROR_ONLY_ONE_BUILDING_ALLOWED_PER_TOWN :{WHITE}... only one building of this type is allowed in a town. +STR_ERROR_BUILDING_NOT_ALLOWED :{WHITE}... the building is not allowed. # Industry related errors STR_ERROR_TOO_MANY_INDUSTRIES :{WHITE}... too many industries diff --git a/src/newgrf_house.cpp b/src/newgrf_house.cpp index 6c9c614af9..2d7c666607 100644 --- a/src/newgrf_house.cpp +++ b/src/newgrf_house.cpp @@ -14,6 +14,7 @@ #include "landscape.h" #include "newgrf_house.h" #include "newgrf_spritegroup.h" +#include "newgrf_text.h" #include "newgrf_town.h" #include "newgrf_sound.h" #include "company_func.h" @@ -26,6 +27,8 @@ #include "safeguards.h" +#include "table/strings.h" + static BuildingCounts _building_counts; static HouseClassMapping _class_mapping[HOUSE_CLASS_MAX]; @@ -67,7 +70,7 @@ static const GRFFile *GetHouseSpecGrf(HouseID house_id) /** * Construct a resolver for a house. * @param house_id House to query. - * @param tile %Tile containing the house. + * @param tile %Tile containing the house. INVALID_TILE to query a house type rather then a certian house tile. * @param town %Town containing the house. * @param callback Callback ID. * @param param1 First parameter (var 10) of the callback. @@ -79,13 +82,28 @@ static const GRFFile *GetHouseSpecGrf(HouseID house_id) HouseResolverObject::HouseResolverObject(HouseID house_id, TileIndex tile, Town *town, CallbackID callback, uint32 param1, uint32 param2, bool not_yet_constructed, uint8 initial_random_bits, uint32 watched_cargo_triggers) - : ResolverObject(GetHouseSpecGrf(house_id), callback, param1, param2), - house_scope(*this, house_id, tile, town, not_yet_constructed, initial_random_bits, watched_cargo_triggers), - town_scope(*this, town, not_yet_constructed) // Don't access StorePSA if house is not yet constructed. + : ResolverObject(GetHouseSpecGrf(house_id), callback, param1, param2) { + assert((tile != INVALID_TILE) == (town != NULL)); + assert(tile == INVALID_TILE || (not_yet_constructed ? IsValidTile(tile) : GetHouseType(tile) == house_id && Town::GetByTile(tile) == town)); + + this->house_scope = (tile != INVALID_TILE) ? + (ScopeResolver*)new HouseScopeResolver(*this, house_id, tile, town, not_yet_constructed, initial_random_bits, watched_cargo_triggers) : + (ScopeResolver*)new FakeHouseScopeResolver(*this, house_id); + + this->town_scope = (town != NULL) ? + (ScopeResolver*)new TownScopeResolver(*this, town, not_yet_constructed) : // Don't access StorePSA if house is not yet constructed. + (ScopeResolver*)new FakeTownScopeResolver(*this); + this->root_spritegroup = HouseSpec::Get(house_id)->grf_prop.spritegroup[0]; } +/* virtual */ HouseResolverObject::~HouseResolverObject() +{ + delete this->house_scope; + delete this->town_scope; +} + HouseClassID AllocateHouseClassID(byte grf_class_id, uint32 grfid) { /* Start from 1 because 0 means that no class has been assigned. */ @@ -435,29 +453,120 @@ static uint32 GetDistanceFromNearbyHouse(uint8 parameter, TileIndex tile, HouseI return UINT_MAX; } + +/** + * @note Used by the resolver to get values for feature 07 deterministic spritegroups. + */ +/* virtual */ uint32 FakeHouseScopeResolver::GetVariable(byte variable, uint32 parameter, bool *available) const +{ + switch (variable) { + /* Construction stage. */ + case 0x40: return TOWN_HOUSE_COMPLETED; + + /* Building age. */ + case 0x41: return 0; + + /* Town zone */ + case 0x42: return FIND_FIRST_BIT(HouseSpec::Get(this->house_id)->building_availability & HZ_ZONALL); // first available + + /* Terrain type */ + case 0x43: return _settings_game.game_creation.landscape == LT_ARCTIC && (HouseSpec::Get(house_id)->building_availability & (HZ_SUBARTC_ABOVE | HZ_SUBARTC_BELOW)) == HZ_SUBARTC_ABOVE ? 4 : 0; + + /* Number of this type of building on the map. */ + case 0x44: return 0; + + /* Whether the town is being created or just expanded. */ + case 0x45: return 0; + + /* Current animation frame. */ + case 0x46: return 0; + + /* Position of the house */ + case 0x47: return 0xFFFFFFFF; + + /* Building counts for old houses with id = parameter. */ + case 0x60: return 0; + + /* Building counts for new houses with id = parameter. */ + case 0x61: return 0; + + /* Land info for nearby tiles. */ + case 0x62: return 0; + + /* Current animation frame of nearby house tiles */ + case 0x63: return 0; + + /* Cargo acceptance history of nearby stations */ + case 0x64: return 0; + + /* Distance test for some house types */ + case 0x65: return 0; + + /* Class and ID of nearby house tile */ + case 0x66: return 0xFFFFFFFF; + + /* GRFID of nearby house tile */ + case 0x67: return 0xFFFFFFFF; + } + + DEBUG(grf, 1, "Unhandled house variable 0x%X", variable); + + *available = false; + return UINT_MAX; +} + uint16 GetHouseCallback(CallbackID callback, uint32 param1, uint32 param2, HouseID house_id, Town *town, TileIndex tile, bool not_yet_constructed, uint8 initial_random_bits, uint32 watched_cargo_triggers) { - assert(IsValidTile(tile) && (not_yet_constructed || IsTileType(tile, MP_HOUSE))); - HouseResolverObject object(house_id, tile, town, callback, param1, param2, not_yet_constructed, initial_random_bits, watched_cargo_triggers); return object.ResolveCallback(); } -static void DrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *group, byte stage, HouseID house_id) +/** + * Get the name of a house. + * @param house House type. + * @param tile Tile where the house is located. INVALID_TILE to get the general name of houses of the given type. + * @return Name of the house. + */ +StringID GetHouseName(HouseID house_id, TileIndex tile) { - const DrawTileSprites *dts = group->ProcessRegisters(&stage); + const HouseSpec *hs = HouseSpec::Get(house_id); + bool house_completed = (tile == INVALID_TILE) || IsHouseCompleted(tile); + Town *t = (tile == INVALID_TILE) ? NULL : Town::GetByTile(tile); + uint16 callback_res = GetHouseCallback(CBID_HOUSE_CUSTOM_NAME, house_completed ? 1 : 0, 0, house_id, t, tile); + if (callback_res != CALLBACK_FAILED && callback_res != 0x400) { + if (callback_res > 0x400) { + ErrorUnknownCallbackResult(hs->grf_prop.grffile->grfid, CBID_HOUSE_CUSTOM_NAME, callback_res); + } else { + StringID ret = GetGRFStringID(hs->grf_prop.grffile->grfid, 0xD000 + callback_res); + if (ret != STR_NULL && ret != STR_UNDEFINED) return ret; + } + } + + return hs->building_name; +} + +static inline PaletteID GetHouseColour(HouseID house_id, TileIndex tile = INVALID_TILE) +{ const HouseSpec *hs = HouseSpec::Get(house_id); - PaletteID palette = hs->random_colour[TileHash2Bit(ti->x, ti->y)] + PALETTE_RECOLOUR_START; if (HasBit(hs->callback_mask, CBM_HOUSE_COLOUR)) { - uint16 callback = GetHouseCallback(CBID_HOUSE_COLOUR, 0, 0, house_id, Town::GetByTile(ti->tile), ti->tile); + Town *t = (tile != INVALID_TILE) ? Town::GetByTile(tile) : NULL; + uint16 callback = GetHouseCallback(CBID_HOUSE_COLOUR, 0, 0, house_id, t, tile); if (callback != CALLBACK_FAILED) { /* If bit 14 is set, we should use a 2cc colour map, else use the callback value. */ - palette = HasBit(callback, 14) ? GB(callback, 0, 8) + SPR_2CCMAP_BASE : callback; + return HasBit(callback, 14) ? GB(callback, 0, 8) + SPR_2CCMAP_BASE : callback; } } + return hs->random_colour[TileHash2Bit(TileX(tile), TileY(tile))] + PALETTE_RECOLOUR_START; +} + +static void DrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *group, byte stage, HouseID house_id) +{ + const DrawTileSprites *dts = group->ProcessRegisters(&stage); + + PaletteID palette = GetHouseColour(house_id, ti->tile); SpriteID image = dts->ground.sprite; PaletteID pal = dts->ground.pal; @@ -472,6 +581,26 @@ static void DrawTileLayout(const TileInfo *ti, const TileLayoutSpriteGroup *grou DrawNewGRFTileSeq(ti, dts, TO_HOUSES, stage, palette); } +static void DrawTileLayoutInGUI(int x, int y, const TileLayoutSpriteGroup *group, HouseID house_id, bool ground) +{ + byte stage = TOWN_HOUSE_COMPLETED; + const DrawTileSprites *dts = group->ProcessRegisters(&stage); + + PaletteID palette = GetHouseColour(house_id); + + if (ground) { + PalSpriteID image = dts->ground; + if (HasBit(image.sprite, SPRITE_MODIFIER_CUSTOM_SPRITE)) image.sprite += stage; + if (HasBit(image.pal, SPRITE_MODIFIER_CUSTOM_SPRITE)) image.pal += stage; + + if (GB(image.sprite, 0, SPRITE_WIDTH) != 0) { + DrawSprite(image.sprite, GroundSpritePaletteTransform(image.sprite, image.pal, palette), x, y); + } + } else { + DrawNewGRFTileSeqInGUI(x, y, dts, stage, palette); + } +} + void DrawNewHouseTile(TileInfo *ti, HouseID house_id) { const HouseSpec *hs = HouseSpec::Get(house_id); @@ -498,6 +627,15 @@ void DrawNewHouseTile(TileInfo *ti, HouseID house_id) } } +void DrawNewHouseTileInGUI(int x, int y, HouseID house_id, bool ground) +{ + HouseResolverObject object(house_id); + const SpriteGroup *group = object.Resolve(); + if (group != NULL && group->type == SGT_TILELAYOUT) { + DrawTileLayoutInGUI(x, y, (const TileLayoutSpriteGroup*)group, house_id, ground); + } +} + /* Simple wrapper for GetHouseCallback to keep the animation unified. */ uint16 GetSimpleHouseCallback(CallbackID callback, uint32 param1, uint32 param2, const HouseSpec *spec, Town *town, TileIndex tile, uint32 extra_data) { @@ -530,6 +668,26 @@ void AnimateNewHouseConstruction(TileIndex tile) } } +/** + * Check if GRF allows a given house to be constructed (callback 17) + * @param house_id house type + * @param tile tile where the house is about to be placed + * @param t town in which we are building + * @param random_bits feature random bits for the house + * @return false if callback 17 disallows construction, true in other cases + */ +bool HouseAllowsConstruction(HouseID house_id, TileIndex tile, Town *t, byte random_bits) +{ + const HouseSpec *hs = HouseSpec::Get(house_id); + if (HasBit(hs->callback_mask, CBM_HOUSE_ALLOW_CONSTRUCTION)) { + uint16 callback_res = GetHouseCallback(CBID_HOUSE_ALLOW_CONSTRUCTION, 0, 0, house_id, t, tile, true, random_bits); + if (callback_res != CALLBACK_FAILED && !Convert8bitBooleanCallback(hs->grf_prop.grffile, CBID_HOUSE_ALLOW_CONSTRUCTION, callback_res)) { + return false; + } + } + return true; +} + bool CanDeleteHouse(TileIndex tile) { const HouseSpec *hs = HouseSpec::Get(GetHouseType(tile)); diff --git a/src/newgrf_house.h b/src/newgrf_house.h index 37c1679470..a1bf0d79ca 100644 --- a/src/newgrf_house.h +++ b/src/newgrf_house.h @@ -36,20 +36,43 @@ struct HouseScopeResolver : public ScopeResolver { /* virtual */ void SetTriggers(int triggers) const; }; +/** + * Fake scope resolver for nonexistent houses. + * + * The purpose of this class is to provide a house resolver for a given house type + * but not an actual house instatntion. We need this when e.g. drawing houses in + * GUI to keep backward compatibility with GRFs that were created before this + * functionality. When querying house sprites, certain GRF may read various house + * variables e.g. the town zone where the building is located or the XY coordinates. + * Since the building doesn't exists we have no real values that we can return. + * Instead of failing, this resolver will return fake values. + */ +struct FakeHouseScopeResolver : public ScopeResolver { + HouseID house_id; ///< Type of house being queried. + + FakeHouseScopeResolver(ResolverObject &ro, HouseID house_id) + : ScopeResolver(ro), house_id(house_id) + { } + + /* virtual */ uint32 GetVariable(byte variable, uint32 parameter, bool *available) const; +}; + /** Resolver object to be used for houses (feature 07 spritegroups). */ struct HouseResolverObject : public ResolverObject { - HouseScopeResolver house_scope; - TownScopeResolver town_scope; + ScopeResolver *house_scope; + ScopeResolver *town_scope; - HouseResolverObject(HouseID house_id, TileIndex tile, Town *town, + HouseResolverObject(HouseID house_id, TileIndex tile = INVALID_TILE, Town *town = NULL, CallbackID callback = CBID_NO_CALLBACK, uint32 param1 = 0, uint32 param2 = 0, bool not_yet_constructed = false, uint8 initial_random_bits = 0, uint32 watched_cargo_triggers = 0); + /* virtual */ ~HouseResolverObject(); + /* virtual */ ScopeResolver *GetScope(VarSpriteGroupScope scope = VSG_SCOPE_SELF, byte relative = 0) { switch (scope) { - case VSG_SCOPE_SELF: return &this->house_scope; - case VSG_SCOPE_PARENT: return &this->town_scope; + case VSG_SCOPE_SELF: return this->house_scope; + case VSG_SCOPE_PARENT: return this->town_scope; default: return ResolverObject::GetScope(scope, relative); } } @@ -80,13 +103,15 @@ void IncreaseBuildingCount(Town *t, HouseID house_id); void DecreaseBuildingCount(Town *t, HouseID house_id); void DrawNewHouseTile(TileInfo *ti, HouseID house_id); +void DrawNewHouseTileInGUI(int x, int y, HouseID house_id, bool ground); void AnimateNewHouseTile(TileIndex tile); void AnimateNewHouseConstruction(TileIndex tile); -uint16 GetHouseCallback(CallbackID callback, uint32 param1, uint32 param2, HouseID house_id, Town *town, TileIndex tile, +uint16 GetHouseCallback(CallbackID callback, uint32 param1, uint32 param2, HouseID house_id, Town *town = NULL, TileIndex tile = INVALID_TILE, bool not_yet_constructed = false, uint8 initial_random_bits = 0, uint32 watched_cargo_triggers = 0); void WatchedCargoCallback(TileIndex tile, uint32 trigger_cargoes); +bool HouseAllowsConstruction(HouseID house_id, TileIndex tile, Town *t, byte random_bits); bool CanDeleteHouse(TileIndex tile); bool NewHouseTileLoop(TileIndex tile); diff --git a/src/newgrf_town.cpp b/src/newgrf_town.cpp index 2f48eb757d..a084b9c753 100644 --- a/src/newgrf_town.cpp +++ b/src/newgrf_town.cpp @@ -162,6 +162,30 @@ TownScopeResolver::TownScopeResolver(ResolverObject &ro, Town *t, bool readonly) t->psa_list.push_back(psa); } +/* virtual */ uint32 FakeTownScopeResolver::GetVariable(byte variable, uint32 parameter, bool *available) const +{ + switch (variable) { + /* Town index */ + case 0x41: return 0xFFFF; + + case 0x40: case 0x7C: case 0x80: case 0x81: case 0x82: case 0x83: case 0x8A: case 0x92: + case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9A: + case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: case 0xA0: case 0xA1: case 0xA2: + case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: case 0xA8: case 0xA9: case 0xAA: + case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xB2: case 0xB6: case 0xB9: case 0xBA: + case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: case 0xC0: case 0xC1: case 0xC2: + case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: case 0xC8: case 0xC9: case 0xCA: + case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: case 0xD0: case 0xD1: case 0xD2: + case 0xD3: case 0xD4: case 0xD5: + return 0; + } + + DEBUG(grf, 1, "Unhandled town variable 0x%X", variable); + + *available = false; + return UINT_MAX; +} + /** * Resolver for a town. * @param grffile NewGRF file associated with the town. diff --git a/src/newgrf_town.h b/src/newgrf_town.h index 145571e8ae..b14a174f40 100644 --- a/src/newgrf_town.h +++ b/src/newgrf_town.h @@ -31,6 +31,24 @@ struct TownScopeResolver : public ScopeResolver { virtual void StorePSA(uint reg, int32 value); }; +/** + * Fake scope resolver for nonexistent towns. + * + * The purpose of this class is to provide a house resolver for a given house type + * but not an actual house instatntion. We need this when e.g. drawing houses in + * GUI to keep backward compatibility with GRFs that were created before this + * functionality. When querying house sprites, certain GRF may read various town + * variables e.g. the population. Since the building doesn't exists and is not + * bounded to any town we have no real values that we can return. Instead of + * failing, this resolver will return fake values. + */ +struct FakeTownScopeResolver : public ScopeResolver { + FakeTownScopeResolver(ResolverObject &ro) : ScopeResolver(ro) + { } + + virtual uint32 GetVariable(byte variable, uint32 parameter, bool *available) const; +}; + /** Resolver of town properties. */ struct TownResolverObject : public ResolverObject { TownScopeResolver town_scope; ///< Scope resolver specific for towns. diff --git a/src/script/api/game/game_window.hpp.sq b/src/script/api/game/game_window.hpp.sq index 23627ca665..ff23f77c78 100644 --- a/src/script/api/game/game_window.hpp.sq +++ b/src/script/api/game/game_window.hpp.sq @@ -68,6 +68,7 @@ void SQGSWindow_Register(Squirrel *engine) SQGSWindow.DefSQConst(engine, ScriptWindow::WC_COMPANY_COLOUR, "WC_COMPANY_COLOUR"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_COMPANY_MANAGER_FACE, "WC_COMPANY_MANAGER_FACE"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_SELECT_STATION, "WC_SELECT_STATION"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WC_SELECT_TOWN, "WC_SELECT_TOWN"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_NEWS_WINDOW, "WC_NEWS_WINDOW"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_TOWN_DIRECTORY, "WC_TOWN_DIRECTORY"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_SUBSIDIES_LIST, "WC_SUBSIDIES_LIST"); @@ -90,6 +91,7 @@ void SQGSWindow_Register(Squirrel *engine) SQGSWindow.DefSQConst(engine, ScriptWindow::WC_INDUSTRY_VIEW, "WC_INDUSTRY_VIEW"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_COMPANY, "WC_COMPANY"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_BUILD_OBJECT, "WC_BUILD_OBJECT"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WC_BUILD_HOUSE, "WC_BUILD_HOUSE"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_BUILD_VEHICLE, "WC_BUILD_VEHICLE"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_BUILD_BRIDGE, "WC_BUILD_BRIDGE"); SQGSWindow.DefSQConst(engine, ScriptWindow::WC_BUILD_STATION, "WC_BUILD_STATION"); @@ -1139,6 +1141,7 @@ void SQGSWindow_Register(Squirrel *engine) SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_PLACE_ROCKS, "WID_ETT_PLACE_ROCKS"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_PLACE_DESERT, "WID_ETT_PLACE_DESERT"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_PLACE_OBJECT, "WID_ETT_PLACE_OBJECT"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_PLACE_HOUSE, "WID_ETT_PLACE_HOUSE"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_BUTTONS_END, "WID_ETT_BUTTONS_END"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_INCREASE_SIZE, "WID_ETT_INCREASE_SIZE"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ETT_DECREASE_SIZE, "WID_ETT_DECREASE_SIZE"); @@ -1249,6 +1252,26 @@ void SQGSWindow_Register(Squirrel *engine) SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TF_LAYOUT_GRID2, "WID_TF_LAYOUT_GRID2"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TF_LAYOUT_GRID3, "WID_TF_LAYOUT_GRID3"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TF_LAYOUT_RANDOM, "WID_TF_LAYOUT_RANDOM"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_CAPTION, "WID_HP_CAPTION"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_MAIN_PANEL_SEL, "WID_HP_MAIN_PANEL_SEL"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SETS_SEL, "WID_HP_HOUSE_SETS_SEL"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SETS, "WID_HP_HOUSE_SETS"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SELECT_MATRIX, "WID_HP_HOUSE_SELECT_MATRIX"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SELECT_SCROLL, "WID_HP_HOUSE_SELECT_SCROLL"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SELECT, "WID_HP_HOUSE_SELECT"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_PREVIEW, "WID_HP_HOUSE_PREVIEW"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_NAME, "WID_HP_HOUSE_NAME"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HISTORICAL_BUILDING, "WID_HP_HISTORICAL_BUILDING"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_POPULATION, "WID_HP_HOUSE_POPULATION"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_ZONES, "WID_HP_HOUSE_ZONES"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_LANDSCAPE, "WID_HP_HOUSE_LANDSCAPE"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_LANDSCAPE_SEL, "WID_HP_HOUSE_LANDSCAPE_SEL"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_YEARS, "WID_HP_HOUSE_YEARS"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_ACCEPTANCE, "WID_HP_HOUSE_ACCEPTANCE"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_HP_HOUSE_SUPPLY, "WID_HP_HOUSE_SUPPLY"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ST_CAPTION, "WID_ST_CAPTION"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ST_PANEL, "WID_ST_PANEL"); + SQGSWindow.DefSQConst(engine, ScriptWindow::WID_ST_SCROLLBAR, "WID_ST_SCROLLBAR"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TT_BEGIN, "WID_TT_BEGIN"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TT_SIGNS, "WID_TT_SIGNS"); SQGSWindow.DefSQConst(engine, ScriptWindow::WID_TT_TREES, "WID_TT_TREES"); diff --git a/src/script/api/script_window.hpp b/src/script/api/script_window.hpp index 58e114734e..9b41adf605 100644 --- a/src/script/api/script_window.hpp +++ b/src/script/api/script_window.hpp @@ -319,6 +319,12 @@ public: */ WC_SELECT_STATION = ::WC_SELECT_STATION, + /** + * Select town (when placing a house); %Window numbers: + * - 0 = #SelectTownWidgets + */ + WC_SELECT_TOWN = ::WC_SELECT_TOWN, + /** * News window; %Window numbers: * - 0 = #NewsWidgets @@ -453,6 +459,12 @@ public: */ WC_BUILD_OBJECT = ::WC_BUILD_OBJECT, + /** + * Build house; %Window numbers: + * - 0 = #BuildHouseWidgets + */ + WC_BUILD_HOUSE = ::WC_BUILD_HOUSE, + /** * Build vehicle; %Window numbers: * - #VehicleType = #BuildVehicleWidgets @@ -2330,6 +2342,7 @@ public: WID_ETT_PLACE_ROCKS = ::WID_ETT_PLACE_ROCKS, ///< Place rocks button. WID_ETT_PLACE_DESERT = ::WID_ETT_PLACE_DESERT, ///< Place desert button (in tropical climate). WID_ETT_PLACE_OBJECT = ::WID_ETT_PLACE_OBJECT, ///< Place transmitter button. + WID_ETT_PLACE_HOUSE = ::WID_ETT_PLACE_HOUSE, ///< Place house button. WID_ETT_BUTTONS_END = ::WID_ETT_BUTTONS_END, ///< End of pushable buttons. WID_ETT_INCREASE_SIZE = ::WID_ETT_INCREASE_SIZE, ///< Upwards arrow button to increase terraforming size. WID_ETT_DECREASE_SIZE = ::WID_ETT_DECREASE_SIZE, ///< Downwards arrow button to decrease terraforming size. @@ -2474,6 +2487,34 @@ public: WID_TF_LAYOUT_RANDOM = ::WID_TF_LAYOUT_RANDOM, ///< Selection for a randomly chosen town layout. }; + /** Widgets of the #HousePickerWindow class. */ + enum HousePickerWidgets { + WID_HP_CAPTION = ::WID_HP_CAPTION, + WID_HP_MAIN_PANEL_SEL = ::WID_HP_MAIN_PANEL_SEL, ///< Selection widget to show/hide the main panel. + WID_HP_HOUSE_SETS_SEL = ::WID_HP_HOUSE_SETS_SEL, ///< Selection widget to show/hide the list of house sets. + WID_HP_HOUSE_SETS = ::WID_HP_HOUSE_SETS, ///< List of available house sets. + WID_HP_HOUSE_SELECT_MATRIX = ::WID_HP_HOUSE_SELECT_MATRIX, ///< Matrix with houses to select. + WID_HP_HOUSE_SELECT_SCROLL = ::WID_HP_HOUSE_SELECT_SCROLL, ///< Scrollbar associated with the house matrix. + WID_HP_HOUSE_SELECT = ::WID_HP_HOUSE_SELECT, ///< Panels with house images in the house matrix. + WID_HP_HOUSE_PREVIEW = ::WID_HP_HOUSE_PREVIEW, ///< House preview panel. + WID_HP_HOUSE_NAME = ::WID_HP_HOUSE_NAME, ///< House name display. + WID_HP_HISTORICAL_BUILDING = ::WID_HP_HISTORICAL_BUILDING, ///< "Historical building" label. + WID_HP_HOUSE_POPULATION = ::WID_HP_HOUSE_POPULATION, ///< House population display. + WID_HP_HOUSE_ZONES = ::WID_HP_HOUSE_ZONES, ///< House zones display. + WID_HP_HOUSE_LANDSCAPE = ::WID_HP_HOUSE_LANDSCAPE, ///< Information about house availability against the landscape. + WID_HP_HOUSE_LANDSCAPE_SEL = ::WID_HP_HOUSE_LANDSCAPE_SEL, ///< Selection widget to show/hide the landscape info. + WID_HP_HOUSE_YEARS = ::WID_HP_HOUSE_YEARS, ///< Years display. + WID_HP_HOUSE_ACCEPTANCE = ::WID_HP_HOUSE_ACCEPTANCE, ///< Cargo accepted. + WID_HP_HOUSE_SUPPLY = ::WID_HP_HOUSE_SUPPLY, ///< Cargo supplied. + }; + + /** Widgets of the #SelectTownWindow class. */ + enum SelectTownWidgets { + WID_ST_CAPTION = ::WID_ST_CAPTION, ///< Caption of the window. + WID_ST_PANEL = ::WID_ST_PANEL, ///< Main panel. + WID_ST_SCROLLBAR = ::WID_ST_SCROLLBAR, ///< Scrollbar of the panel. + }; + /* automatically generated from ../../widgets/transparency_widget.h */ /** Widgets of the #TransparenciesWindow class. */ enum TransparencyToolbarWidgets { diff --git a/src/script/api/template/template_window.hpp.sq b/src/script/api/template/template_window.hpp.sq index a21a75ab89..445e90fe4e 100644 --- a/src/script/api/template/template_window.hpp.sq +++ b/src/script/api/template/template_window.hpp.sq @@ -233,6 +233,10 @@ namespace SQConvert { template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::TownViewWidgets res) { sq_pushinteger(vm, (int32)res); return 1; } template <> inline ScriptWindow::TownFoundingWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::TownFoundingWidgets)tmp; } template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::TownFoundingWidgets res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> inline ScriptWindow::HousePickerWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::HousePickerWidgets)tmp; } + template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::HousePickerWidgets res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> inline ScriptWindow::SelectTownWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::SelectTownWidgets)tmp; } + template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::SelectTownWidgets res) { sq_pushinteger(vm, (int32)res); return 1; } template <> inline ScriptWindow::TransparencyToolbarWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::TransparencyToolbarWidgets)tmp; } template <> inline int Return(HSQUIRRELVM vm, ScriptWindow::TransparencyToolbarWidgets res) { sq_pushinteger(vm, (int32)res); return 1; } template <> inline ScriptWindow::BuildTreesWidgets GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptWindow::BuildTreesWidgets)tmp; } diff --git a/src/terraform_gui.cpp b/src/terraform_gui.cpp index acc277249c..be774dc92a 100644 --- a/src/terraform_gui.cpp +++ b/src/terraform_gui.cpp @@ -32,6 +32,7 @@ #include "hotkeys.h" #include "engine_base.h" #include "terraform_gui.h" +#include "town_gui.h" #include "zoom_func.h" #include "widgets/terraform_widget.h" @@ -465,6 +466,9 @@ static const NWidgetPart _nested_scen_edit_land_gen_widgets[] = { NWidget(WWT_IMGBTN, COLOUR_GREY, WID_ETT_PLACE_OBJECT), SetMinimalSize(23, 22), SetFill(0, 1), SetDataTip(SPR_IMG_TRANSMITTER, STR_SCENEDIT_TOOLBAR_PLACE_OBJECT), NWidget(NWID_SPACER), SetFill(1, 0), + NWidget(WWT_IMGBTN, COLOUR_GREY, WID_ETT_PLACE_HOUSE), SetMinimalSize(23, 22), + SetFill(0, 1), SetDataTip(SPR_IMG_TOWN, STR_SCENEDIT_TOOLBAR_PLACE_HOUSE), + NWidget(NWID_SPACER), SetFill(1, 0), EndContainer(), NWidget(NWID_HORIZONTAL), NWidget(NWID_SPACER), SetFill(1, 0), @@ -609,6 +613,13 @@ struct ScenarioEditorLandscapeGenerationWindow : Window { ShowBuildObjectPicker(); break; + case WID_ETT_PLACE_HOUSE: // Place house button + if (HandlePlacePushButton(this, WID_ETT_PLACE_HOUSE, SPR_CURSOR_TOWN, HT_RECT)) { + ShowBuildHousePicker(this); + this->last_user_action = widget; + } + break; + case WID_ETT_INCREASE_SIZE: case WID_ETT_DECREASE_SIZE: { // Increase/Decrease terraform size int size = (widget == WID_ETT_INCREASE_SIZE) ? 1 : -1; @@ -674,6 +685,10 @@ struct ScenarioEditorLandscapeGenerationWindow : Window { VpStartPlaceSizing(tile, VPM_X_AND_Y, DDSP_CREATE_DESERT); break; + case WID_ETT_PLACE_HOUSE: // Place house button + PlaceProc_House(tile); + break; + default: NOT_REACHED(); } } @@ -705,6 +720,8 @@ struct ScenarioEditorLandscapeGenerationWindow : Window { this->RaiseButtons(); this->SetDirty(); DeleteWindowById(WC_BUILD_OBJECT, 0); + DeleteWindowById(WC_BUILD_HOUSE, 0); + DeleteWindowById(WC_SELECT_STATION, 0); } static HotkeyList hotkeys; @@ -731,6 +748,7 @@ static Hotkey terraform_editor_hotkeys[] = { Hotkey('R', "rocky", WID_ETT_PLACE_ROCKS), Hotkey('T', "desert", WID_ETT_PLACE_DESERT), Hotkey('O', "object", WID_ETT_PLACE_OBJECT), + Hotkey('H', "house", WID_ETT_PLACE_HOUSE), HOTKEY_LIST_END }; diff --git a/src/town.h b/src/town.h index 010c7c2168..06edbe80ad 100644 --- a/src/town.h +++ b/src/town.h @@ -188,6 +188,7 @@ void UpdateTownCargoBitmap(); CommandCost CheckIfAuthorityAllowsNewStation(TileIndex tile, DoCommandFlag flags); Town *ClosestTownFromTile(TileIndex tile, uint threshold); void ChangeTownRating(Town *t, int add, int max, DoCommandFlag flags); +HouseZonesBits TryGetTownRadiusGroup(const Town *t, TileIndex tile); HouseZonesBits GetTownRadiusGroup(const Town *t, TileIndex tile); void SetTownRatingTestMode(bool mode); uint GetMaskOfTownActions(int *nump, CompanyID cid, const Town *t); diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index ea44603b0c..8f7f070ec2 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -25,7 +25,6 @@ #include "genworld.h" #include "newgrf_debug.h" #include "newgrf_house.h" -#include "newgrf_text.h" #include "autoslope.h" #include "tunnelbridge_map.h" #include "strings_func.h" @@ -46,6 +45,7 @@ #include "object_base.h" #include "ai/ai.hpp" #include "game/game.hpp" +#include "zoom_func.h" #include "table/strings.h" #include "table/town_land.h" @@ -189,6 +189,11 @@ static void TownDrawHouseLift(const TileInfo *ti) AddChildSpriteScreen(SPR_LIFT, PAL_NONE, 14, 60 - GetLiftPosition(ti->tile)); } +static void DrawHouseLiftInGUI(int x, int y) +{ + DrawSprite(SPR_LIFT, PAL_NONE, x - 18, y + 7); +} + typedef void TownDrawTileProc(const TileInfo *ti); static TownDrawTileProc * const _town_draw_tile_procs[1] = { TownDrawHouseLift @@ -258,6 +263,85 @@ static void DrawTile_Town(TileInfo *ti) } } +static void DrawOldHouseTileInGUI(int x, int y, HouseID house_id, bool ground) +{ + /* Retrieve pointer to the draw town tile struct */ + const DrawBuildingsTileStruct *dcts = &_town_draw_tile_data[house_id << 4 | TOWN_HOUSE_COMPLETED]; + if (ground) { + /* Draw the ground sprite */ + DrawSprite(dcts->ground.sprite, dcts->ground.pal, x, y); + } else { + /* Add a house on top of the ground? */ + if (dcts->building.sprite != 0) { + DrawSprite(dcts->building.sprite, dcts->building.pal, x + dcts->subtile_x, y + dcts->subtile_y); + } + /* Draw the lift */ + if (dcts->draw_proc == 1) DrawHouseLiftInGUI(x, y); + } +} + +/** + * Draw image of a house. Image will be centered between the \c left and the \c right and verticaly aligned to the \c bottom. + * + * @param house_id house type + * @param left left bound of the drawing area + * @param top top bound of the drawing area + * @param right right bound of the drawing area + * @param bottom bottom bound of the drawing area + */ +void DrawHouseImage(HouseID house_id, int left, int top, int right, int bottom) +{ + DrawPixelInfo tmp_dpi; + if (!FillDrawPixelInfo(&tmp_dpi, left, top, right - left + 1, bottom - top + 1)) return; + DrawPixelInfo *old_dpi = _cur_dpi; + _cur_dpi = &tmp_dpi; + + const HouseSpec *hs = HouseSpec::Get(house_id); + + /* sprites are relative to the topmost pixel of the ground tile */ + uint x = (right - left + 1) / 2; + uint y = bottom - top + 1 - TILE_PIXELS; + if (hs->building_flags & TILE_SIZE_1x2) x -= TILE_PIXELS / 2; + if (hs->building_flags & TILE_SIZE_2x1) x += TILE_PIXELS / 2; + if (hs->building_flags & BUILDING_HAS_2_TILES) y -= TILE_PIXELS / 2; + if (hs->building_flags & BUILDING_HAS_4_TILES) y -= TILE_PIXELS / 2; + + bool new_house = false; + if (house_id >= NEW_HOUSE_OFFSET) { + /* Houses don't necessarily need new graphics. If they don't have a + * spritegroup associated with them, then the sprite for the substitute + * house id is drawn instead. */ + if (hs->grf_prop.spritegroup[0] != NULL) { + new_house = true; + } else { + house_id = hs->grf_prop.subst_id; + } + } + + uint num_row = (hs->building_flags & BUILDING_2_TILES_X) ? 2 : 1; + uint num_col = (hs->building_flags & BUILDING_2_TILES_Y) ? 2 : 1; + + for (bool ground = true; ; ground = !ground) { + HouseID hid = house_id; + for (uint row = 0; row < num_row; row++) { + for (uint col = 0; col < num_col; col++) { + Point offset = RemapCoords(row * TILE_SIZE, col * TILE_SIZE, 0); // offset for current tile + offset.x = UnScaleByZoom(offset.x, ZOOM_LVL_GUI); + offset.y = UnScaleByZoom(offset.y, ZOOM_LVL_GUI); + if (new_house) { + DrawNewHouseTileInGUI(x + offset.x, y + offset.y, hid, ground); + } else { + DrawOldHouseTileInGUI(x + offset.x, y + offset.y, hid, ground); + } + hid++; + } + } + if (!ground) break; + } + + _cur_dpi = old_dpi; +} + static int GetSlopePixelZ_Town(TileIndex tile, uint x, uint y) { return GetTileMaxPixelZ(tile); @@ -574,13 +658,12 @@ static CommandCost ClearTile_Town(TileIndex tile, DoCommandFlag flags) return cost; } -static void AddProducedCargo_Town(TileIndex tile, CargoArray &produced) +void AddProducedHouseCargo(HouseID house_id, TileIndex tile, CargoArray &produced) { - HouseID house_id = GetHouseType(tile); const HouseSpec *hs = HouseSpec::Get(house_id); - Town *t = Town::GetByTile(tile); if (HasBit(hs->callback_mask, CBM_HOUSE_PRODUCE_CARGO)) { + Town *t = (tile == INVALID_TILE) ? NULL : Town::GetByTile(tile); for (uint i = 0; i < 256; i++) { uint16 callback = GetHouseCallback(CBID_HOUSE_PRODUCE_CARGO, i, 0, house_id, t, tile); @@ -601,6 +684,11 @@ static void AddProducedCargo_Town(TileIndex tile, CargoArray &produced) } } +static void AddProducedCargo_Town(TileIndex tile, CargoArray &produced) +{ + AddProducedHouseCargo(GetHouseType(tile), tile, produced); +} + static inline void AddAcceptedCargoSetMask(CargoID cargo, uint amount, CargoArray &acceptance, uint32 *always_accepted) { if (cargo == CT_INVALID || amount == 0) return; @@ -608,9 +696,10 @@ static inline void AddAcceptedCargoSetMask(CargoID cargo, uint amount, CargoArra SetBit(*always_accepted, cargo); } -static void AddAcceptedCargo_Town(TileIndex tile, CargoArray &acceptance, uint32 *always_accepted) +void AddAcceptedHouseCargo(HouseID house_id, TileIndex tile, CargoArray &acceptance, uint32 *always_accepted) { - const HouseSpec *hs = HouseSpec::Get(GetHouseType(tile)); + const HouseSpec *hs = HouseSpec::Get(house_id); + Town *t = (tile == INVALID_TILE) ? NULL : Town::GetByTile(tile); CargoID accepts[3]; /* Set the initial accepted cargo types */ @@ -620,7 +709,7 @@ static void AddAcceptedCargo_Town(TileIndex tile, CargoArray &acceptance, uint32 /* Check for custom accepted cargo types */ if (HasBit(hs->callback_mask, CBM_HOUSE_ACCEPT_CARGO)) { - uint16 callback = GetHouseCallback(CBID_HOUSE_ACCEPT_CARGO, 0, 0, GetHouseType(tile), Town::GetByTile(tile), tile); + uint16 callback = GetHouseCallback(CBID_HOUSE_ACCEPT_CARGO, 0, 0, house_id, t, tile); if (callback != CALLBACK_FAILED) { /* Replace accepted cargo types with translated values from callback */ accepts[0] = GetCargoTranslation(GB(callback, 0, 5), hs->grf_prop.grffile); @@ -631,7 +720,7 @@ static void AddAcceptedCargo_Town(TileIndex tile, CargoArray &acceptance, uint32 /* Check for custom cargo acceptance */ if (HasBit(hs->callback_mask, CBM_HOUSE_CARGO_ACCEPTANCE)) { - uint16 callback = GetHouseCallback(CBID_HOUSE_CARGO_ACCEPTANCE, 0, 0, GetHouseType(tile), Town::GetByTile(tile), tile); + uint16 callback = GetHouseCallback(CBID_HOUSE_CARGO_ACCEPTANCE, 0, 0, house_id, t, tile); if (callback != CALLBACK_FAILED) { AddAcceptedCargoSetMask(accepts[0], GB(callback, 0, 4), acceptance, always_accepted); AddAcceptedCargoSetMask(accepts[1], GB(callback, 4, 4), acceptance, always_accepted); @@ -651,31 +740,23 @@ static void AddAcceptedCargo_Town(TileIndex tile, CargoArray &acceptance, uint32 } } +static void AddAcceptedCargo_Town(TileIndex tile, CargoArray &acceptance, uint32 *always_accepted) +{ + AddAcceptedHouseCargo(GetHouseType(tile), tile, acceptance, always_accepted); +} + static void GetTileDesc_Town(TileIndex tile, TileDesc *td) { const HouseID house = GetHouseType(tile); - const HouseSpec *hs = HouseSpec::Get(house); - bool house_completed = IsHouseCompleted(tile); - td->str = hs->building_name; + td->str = GetHouseName(house, tile); - uint16 callback_res = GetHouseCallback(CBID_HOUSE_CUSTOM_NAME, house_completed ? 1 : 0, 0, house, Town::GetByTile(tile), tile); - if (callback_res != CALLBACK_FAILED && callback_res != 0x400) { - if (callback_res > 0x400) { - ErrorUnknownCallbackResult(hs->grf_prop.grffile->grfid, CBID_HOUSE_CUSTOM_NAME, callback_res); - } else { - StringID new_name = GetGRFStringID(hs->grf_prop.grffile->grfid, 0xD000 + callback_res); - if (new_name != STR_NULL && new_name != STR_UNDEFINED) { - td->str = new_name; - } - } - } - - if (!house_completed) { + if (!IsHouseCompleted(tile)) { SetDParamX(td->dparam, 0, td->str); td->str = STR_LAI_TOWN_INDUSTRY_DESCRIPTION_UNDER_CONSTRUCTION; } + const HouseSpec *hs = HouseSpec::Get(house); if (hs->grf_prop.grffile != NULL) { const GRFConfig *gc = GetGRFConfig(hs->grf_prop.grffile->grfid); td->grf = gc->GetName(); @@ -1992,17 +2073,21 @@ bool GenerateTowns(TownLayout layout) /** * Returns the bit corresponding to the town zone of the specified tile + * or #HZB_END if the tile is ouside of the town. + * * @param t Town on which town zone is to be found * @param tile TileIndex where town zone needs to be found * @return the bit position of the given zone, as defined in HouseZones + * + * @see GetTownRadiusGroup */ -HouseZonesBits GetTownRadiusGroup(const Town *t, TileIndex tile) +HouseZonesBits TryGetTownRadiusGroup(const Town *t, TileIndex tile) { uint dist = DistanceSquare(tile, t->xy); if (t->fund_buildings_months && dist <= 25) return HZB_TOWN_CENTRE; - HouseZonesBits smallest = HZB_TOWN_EDGE; + HouseZonesBits smallest = HZB_END; for (HouseZonesBits i = HZB_BEGIN; i < HZB_END; i++) { if (dist < t->cache.squared_town_zone_radius[i]) smallest = i; } @@ -2010,6 +2095,22 @@ HouseZonesBits GetTownRadiusGroup(const Town *t, TileIndex tile) return smallest; } +/** + * Returns the bit corresponding to the town zone of the specified tile. + * Returns #HZB_TOWN_EDGE if the tile is either in an edge zone or ouside of the town. + * + * @param t Town on which town zone is to be found + * @param tile TileIndex where town zone needs to be found + * @return the bit position of the given zone, as defined in HouseZones + * + * @see TryGetTownRadiusGroup + */ +HouseZonesBits GetTownRadiusGroup(const Town *t, TileIndex tile) +{ + HouseZonesBits ret = TryGetTownRadiusGroup(t, tile); + return ret != HZB_END ? ret : HZB_TOWN_EDGE; +} + /** * Clears tile and builds a house or house part. * @param tile tile index @@ -2061,184 +2162,307 @@ static void MakeTownHouse(TileIndex t, Town *town, byte counter, byte stage, Hou * @param tile tile to check * @param town town that is checking * @param noslope are slopes (foundations) allowed? - * @return true iff house can be built here + * @return success if house can be built here, error message otherwise */ -static inline bool CanBuildHouseHere(TileIndex tile, TownID town, bool noslope) +static inline CommandCost CanBuildHouseHere(TileIndex tile, TownID town, bool noslope) { /* cannot build on these slopes... */ - Slope slope = GetTileSlope(tile); - if ((noslope && slope != SLOPE_FLAT) || IsSteepSlope(slope)) return false; + if (noslope) { + if (!IsTileFlat(tile)) return_cmd_error(STR_ERROR_FLAT_LAND_REQUIRED); + } else { + if (IsSteepSlope(GetTileSlope(tile))) return_cmd_error(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION); + } /* building under a bridge? */ - if (IsBridgeAbove(tile)) return false; + if (IsBridgeAbove(tile)) return_cmd_error(STR_ERROR_MUST_DEMOLISH_BRIDGE_FIRST); + + /* can we clear the land? */ + CommandCost ret = DoCommand(tile, 0, 0, DC_AUTO | DC_NO_WATER, CMD_LANDSCAPE_CLEAR); + if (ret.Failed()) return ret; /* do not try to build over house owned by another town */ - if (IsTileType(tile, MP_HOUSE) && GetTownIndex(tile) != town) return false; + if (IsTileType(tile, MP_HOUSE) && GetTownIndex(tile) != town) return CMD_ERROR; - /* can we clear the land? */ - return DoCommand(tile, 0, 0, DC_AUTO | DC_NO_WATER, CMD_LANDSCAPE_CLEAR).Succeeded(); + return CommandCost(); } /** - * Checks if a house can be built at this tile, must have the same max z as parameter. - * @param tile tile to check + * Checks if a house can be built here. Important is slope, bridge above + * and ability to clear the land. + * + * @param ta tile area to check * @param town town that is checking - * @param z max z of this tile so more parts of a house are at the same height (with foundation) + * @param maxz z level of the house, check if all tiles have this max z level * @param noslope are slopes (foundations) allowed? - * @return true iff house can be built here - * @see CanBuildHouseHere() + * @return success if house can be built here, error message otherwise + * + * @see TownLayoutAllowsHouseHere */ -static inline bool CheckBuildHouseSameZ(TileIndex tile, TownID town, int z, bool noslope) +static inline CommandCost CanBuildHouseHere(const TileArea &ta, TownID town, int maxz, bool noslope) { - if (!CanBuildHouseHere(tile, town, noslope)) return false; + TILE_AREA_LOOP(tile, ta) { + CommandCost ret = CanBuildHouseHere(tile, town, noslope); + /* if building on slopes is allowed, there will be flattening foundation (to tile max z) */ + if (ret.Succeeded() && GetTileMaxZ(tile) != maxz) ret = CommandCost(STR_ERROR_LAND_SLOPED_IN_WRONG_DIRECTION); + if (ret.Failed()) return ret; + } - /* if building on slopes is allowed, there will be flattening foundation (to tile max z) */ - if (GetTileMaxZ(tile) != z) return false; + return CommandCost(); +} - return true; + +/** + * Test whether houses of given type are avaliable in current game. + * + * The function will check whether the house is available at all e.g. is not overriden. + * Also availability for current climate and given house zone will be tested. + * + * @param house house type + * @param above_snowline true to test availability above the snow line, false for below (arctic climate only) + * @param zone return error if houses are forbidden in this house zone + * @return success if house is avaliable, error message otherwise + */ +static inline CommandCost IsHouseTypeAllowed(HouseID house, bool above_snowline, HouseZonesBits zone) + { + const HouseSpec *hs = HouseSpec::Get(house); + /* Disallow disabled and replaced houses. */ + if (!hs->enabled || hs->grf_prop.override != INVALID_HOUSE_ID) return CMD_ERROR; + + /* Check if we can build this house in current climate. */ + if (_settings_game.game_creation.landscape != LT_ARCTIC) { + if (!(hs->building_availability & (HZ_TEMP << _settings_game.game_creation.landscape))) return CMD_ERROR; + } else if (above_snowline) { + if (!(hs->building_availability & HZ_SUBARTC_ABOVE)) return_cmd_error(STR_ERROR_BUILDING_NOT_ALLOWED_ABOVE_SNOW_LINE); + } else { + if (!(hs->building_availability & HZ_SUBARTC_BELOW)) return_cmd_error(STR_ERROR_BUILDING_NOT_ALLOWED_BELOW_SNOW_LINE); + } + + /* Check if the house zone is allowed for this type of houses. */ + if (!HasBit(hs->building_availability & HZ_ZONALL, zone)) { + return_cmd_error(STR_ERROR_BUILDING_NOT_ALLOWED_IN_THIS_TOWN_ZONE); + } + + return CommandCost(); } /** - * Checks if a house of size 2x2 can be built at this tile - * @param tile tile, N corner - * @param town town that is checking - * @param z maximum tile z so all tile have the same max z - * @param noslope are slopes (foundations) allowed? - * @return true iff house can be built - * @see CheckBuildHouseSameZ() + * Check whether a town can hold more house types. + * @param t the town we wan't to check + * @param house type of the house we wan't to add + * @return success if houses of this type are allowed, error message otherwise */ -static bool CheckFree2x2Area(TileIndex tile, TownID town, int z, bool noslope) +static inline CommandCost IsAnotherHouseTypeAllowedInTown(Town *t, HouseID house) { - /* we need to check this tile too because we can be at different tile now */ - if (!CheckBuildHouseSameZ(tile, town, z, noslope)) return false; + const HouseSpec *hs = HouseSpec::Get(house); - for (DiagDirection d = DIAGDIR_SE; d < DIAGDIR_END; d++) { - tile += TileOffsByDiagDir(d); - if (!CheckBuildHouseSameZ(tile, town, z, noslope)) return false; + /* Don't let these counters overflow. Global counters are 32bit, there will never be that many houses. */ + if (hs->class_id != HOUSE_NO_CLASS) { + /* id_count is always <= class_count, so it doesn't need to be checked */ + if (t->cache.building_counts.class_count[hs->class_id] == UINT16_MAX) return_cmd_error(STR_ERROR_TOO_MANY_HOUSE_SETS); + } else { + /* If the house has no class, check id_count instead */ + if (t->cache.building_counts.id_count[house] == UINT16_MAX) return_cmd_error(STR_ERROR_TOO_MANY_HOUSE_TYPES); } - return true; + return CommandCost(); } - /** * Checks if current town layout allows building here * @param t town - * @param tile tile to check + * @param ta tile area to check * @return true iff town layout allows building here * @note see layouts */ -static inline bool TownLayoutAllowsHouseHere(Town *t, TileIndex tile) +static inline bool TownLayoutAllowsHouseHere(Town *t, const TileArea &ta) { /* Allow towns everywhere when we don't build roads */ if (!_settings_game.economy.allow_town_roads && !_generating_world) return true; - TileIndexDiffC grid_pos = TileIndexToTileIndexDiffC(t->xy, tile); + TileIndexDiffC grid_pos = TileIndexToTileIndexDiffC(t->xy, ta.tile); + const uint overflow = 3 * 4 * UINT16_MAX; // perform "floor division" switch (t->layout) { - case TL_2X2_GRID: - if ((grid_pos.x % 3) == 0 || (grid_pos.y % 3) == 0) return false; - break; + case TL_2X2_GRID: return (uint)(grid_pos.x + overflow) % 3 >= ta.w && (uint)(grid_pos.y + overflow) % 3 >= ta.h; + case TL_3X3_GRID: return (uint)(grid_pos.x + overflow) % 4 >= ta.w && (uint)(grid_pos.y + overflow) % 4 >= ta.h; + default: return true; + } +} - case TL_3X3_GRID: - if ((grid_pos.x % 4) == 0 || (grid_pos.y % 4) == 0) return false; - break; - default: - break; +/** + * Find a suitable place (free of any obstacles) for a new town house. Search around a given location + * taking into account the layout of the town. + * + * @param tile tile that must be included by the building + * @param t the town we are building in + * @param house house type + * @return where the building can be placed, INVALID_TILE if no lacation was found + * + * @pre CanBuildHouseHere(tile, t->index, false) + * + * @see CanBuildHouseHere + */ +static TileIndex FindPlaceForTownHouseAroundTile(TileIndex tile, Town *t, HouseID house) +{ + const HouseSpec *hs = HouseSpec::Get(house); + bool noslope = (hs->building_flags & TILE_NOT_SLOPED) != 0; + + TileArea ta(tile, 1, 1); + DiagDirection dir; + uint count; + if (hs->building_flags & TILE_SIZE_2x2) { + ta.w = ta.h = 2; + dir = DIAGDIR_NW; // 'd' goes through DIAGDIR_NW, DIAGDIR_NE, DIAGDIR_SE + count = 4; + } else if (hs->building_flags & TILE_SIZE_2x1) { + ta.w = 2; + dir = DIAGDIR_NE; + count = 2; + } else if (hs->building_flags & TILE_SIZE_1x2) { + ta.h = 2; + dir = DIAGDIR_NW; + count = 2; + } else { // TILE_SIZE_1x1 + /* CanBuildHouseHere(tile, t->index, false) already checked */ + if (noslope && !IsTileFlat(tile)) return INVALID_TILE; + return tile; } - return true; + int maxz = GetTileMaxZ(tile); + /* Drift around the tile and find a place for the house. For 1x2 and 2x1 houses just two + * positions will be checked (at the exact tile and the other). In case of 2x2 houses + * 4 positions have to be checked (clockwise). */ + while (count-- > 0) { + if (!TownLayoutAllowsHouseHere(t, ta)) continue; + if (CanBuildHouseHere(ta, t->index, maxz, noslope).Succeeded()) return ta.tile; + ta.tile += TileOffsByDiagDir(dir); + dir = ChangeDiagDir(dir, DIAGDIRDIFF_90RIGHT); + } + + return INVALID_TILE; } /** - * Checks if current town layout allows 2x2 building here - * @param t town - * @param tile tile to check - * @return true iff town layout allows 2x2 building here - * @note see layouts + * Check if a given house can be built in a given town. + * @param house house type + * @param t the town + * @return success if house can be built, error message otherwise */ -static inline bool TownLayoutAllows2x2HouseHere(Town *t, TileIndex tile) +static CommandCost CheckCanBuildHouse(HouseID house, const Town *t) { - /* Allow towns everywhere when we don't build roads */ - if (!_settings_game.economy.allow_town_roads && !_generating_world) return true; + const HouseSpec *hs = HouseSpec::Get(house); - /* Compute relative position of tile. (Positive offsets are towards north) */ - TileIndexDiffC grid_pos = TileIndexToTileIndexDiffC(t->xy, tile); + if (_loaded_newgrf_features.has_newhouses && !_generating_world && + _game_mode != GM_EDITOR && (hs->extra_flags & BUILDING_IS_HISTORICAL) != 0) { + return CMD_ERROR; + } - switch (t->layout) { - case TL_2X2_GRID: - grid_pos.x %= 3; - grid_pos.y %= 3; - if ((grid_pos.x != 2 && grid_pos.x != -1) || - (grid_pos.y != 2 && grid_pos.y != -1)) return false; - break; + if (_cur_year > hs->max_year) return_cmd_error(STR_ERROR_BUILDING_IS_TOO_OLD); + if (_cur_year < hs->min_year) return_cmd_error(STR_ERROR_BUILDING_IS_TOO_MODERN); - case TL_3X3_GRID: - if ((grid_pos.x & 3) < 2 || (grid_pos.y & 3) < 2) return false; - break; - - default: - break; + /* Special houses that there can be only one of. */ + if (hs->building_flags & BUILDING_IS_CHURCH) { + if (HasBit(t->flags, TOWN_HAS_CHURCH)) return_cmd_error(STR_ERROR_ONLY_ONE_BUILDING_ALLOWED_PER_TOWN); + } else if (hs->building_flags & BUILDING_IS_STADIUM) { + if (HasBit(t->flags, TOWN_HAS_STADIUM)) return_cmd_error(STR_ERROR_ONLY_ONE_BUILDING_ALLOWED_PER_TOWN); } - return true; + return CommandCost(); } /** - * Checks if 1x2 or 2x1 building is allowed here, also takes into account current town layout - * Also, tests both building positions that occupy this tile - * @param tile tile where the building should be built - * @param t town - * @param maxz all tiles should have the same height - * @param noslope are slopes forbidden? - * @param second diagdir from first tile to second tile + * Really build a house. + * @param t town to build house in + * @param tile house location + * @param house house type + * @param random_bits random bits for the house */ -static bool CheckTownBuild2House(TileIndex *tile, Town *t, int maxz, bool noslope, DiagDirection second) +static void DoBuildHouse(Town *t, TileIndex tile, HouseID house, byte random_bits) { - /* 'tile' is already checked in BuildTownHouse() - CanBuildHouseHere() and slope test */ + t->cache.num_houses++; - TileIndex tile2 = *tile + TileOffsByDiagDir(second); - if (TownLayoutAllowsHouseHere(t, tile2) && CheckBuildHouseSameZ(tile2, t->index, maxz, noslope)) return true; + const HouseSpec *hs = HouseSpec::Get(house); - tile2 = *tile + TileOffsByDiagDir(ReverseDiagDir(second)); - if (TownLayoutAllowsHouseHere(t, tile2) && CheckBuildHouseSameZ(tile2, t->index, maxz, noslope)) { - *tile = tile2; - return true; + /* Special houses that there can be only one of. */ + if (hs->building_flags & BUILDING_IS_CHURCH) { + SetBit(t->flags, TOWN_HAS_CHURCH); + } else if (hs->building_flags & BUILDING_IS_STADIUM) { + SetBit(t->flags, TOWN_HAS_STADIUM); } - return false; -} + byte construction_counter = 0; + byte construction_stage = 0; + + if (_generating_world || _game_mode == GM_EDITOR) { + uint32 r = Random(); + + construction_stage = TOWN_HOUSE_COMPLETED; + if (Chance16(1, 7)) construction_stage = GB(r, 0, 2); + + if (construction_stage == TOWN_HOUSE_COMPLETED) { + ChangePopulation(t, hs->population); + } else { + construction_counter = GB(r, 2, 2); + } + } + MakeTownHouse(tile, t, construction_counter, construction_stage, house, random_bits); + UpdateTownRadius(t); + UpdateTownCargoes(t, tile); +} /** - * Checks if 2x2 building is allowed here, also takes into account current town layout - * Also, tests all four building positions that occupy this tile - * @param tile tile where the building should be built - * @param t town - * @param maxz all tiles should have the same height - * @param noslope are slopes forbidden? + * Place a custom house + * @param tile tile where the house will be located + * @param flags flags for the command + * @param p1 \n + * bits 0..15 - the HouseID of the house \n + * bits 16..31 - the TownID of the town \n + * @param p2 \n + * bits 0..7 - random bits \n + * @param text unused + * @return the cost of this operation or an error */ -static bool CheckTownBuild2x2House(TileIndex *tile, Town *t, int maxz, bool noslope) +CommandCost CmdBuildHouse(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) { - TileIndex tile2 = *tile; + if (_game_mode != GM_EDITOR && // in scenario editor anyone can build a house + _current_company != OWNER_TOWN && // towns naturally can build houses + _current_company != OWNER_DEITY) { // GameScript can place a house too + return CMD_ERROR; + } - for (DiagDirection d = DIAGDIR_SE;; d++) { // 'd' goes through DIAGDIR_SE, DIAGDIR_SW, DIAGDIR_NW, DIAGDIR_END - if (TownLayoutAllows2x2HouseHere(t, tile2) && CheckFree2x2Area(tile2, t->index, maxz, noslope)) { - *tile = tile2; - return true; - } - if (d == DIAGDIR_END) break; - tile2 += TileOffsByDiagDir(ReverseDiagDir(d)); // go clockwise + HouseID house = GB(p1, 0, 16); + Town *t = Town::Get(GB(p1, 16, 16)); + if (t == NULL) return CMD_ERROR; + byte random_bits = GB(p2, 0, 8); + + int max_z = GetTileMaxZ(tile); + bool above_snowline = (_settings_game.game_creation.landscape == LT_ARCTIC) && (max_z > HighestSnowLine()); + + CommandCost ret = IsHouseTypeAllowed(house, above_snowline, TryGetTownRadiusGroup(t, tile)); + if (ret.Succeeded()) ret = IsAnotherHouseTypeAllowedInTown(t, house); + if (ret.Succeeded()) ret = CheckCanBuildHouse(house, t); + if (ret.Succeeded()) { + /* While placing a house manually, try only at exact position and ignore the layout */ + const HouseSpec *hs = HouseSpec::Get(house); + uint w = hs->building_flags & BUILDING_2_TILES_X ? 2 : 1; + uint h = hs->building_flags & BUILDING_2_TILES_Y ? 2 : 1; + bool noslope = (hs->building_flags & TILE_NOT_SLOPED) != 0; + ret = CanBuildHouseHere(TileArea(tile, w, h), t->index, max_z, noslope); } + if (ret.Failed()) return ret; - return false; -} + /* Check if GRF allows this house */ + if (!HouseAllowsConstruction(house, tile, t, random_bits)) return_cmd_error(STR_ERROR_BUILDING_NOT_ALLOWED); + if (flags & DC_EXEC) DoBuildHouse(t, tile, house, random_bits); + return CommandCost(); +} /** * Tries to build a house at this tile @@ -2249,23 +2473,13 @@ static bool CheckTownBuild2x2House(TileIndex *tile, Town *t, int maxz, bool nosl static bool BuildTownHouse(Town *t, TileIndex tile) { /* forbidden building here by town layout */ - if (!TownLayoutAllowsHouseHere(t, tile)) return false; + if (!TownLayoutAllowsHouseHere(t, TileArea(tile, 1, 1))) return false; /* no house allowed at all, bail out */ - if (!CanBuildHouseHere(tile, t->index, false)) return false; - - Slope slope = GetTileSlope(tile); - int maxz = GetTileMaxZ(tile); + if (CanBuildHouseHere(tile, t->index, false).Failed()) return false; - /* Get the town zone type of the current tile, as well as the climate. - * This will allow to easily compare with the specs of the new house to build */ - HouseZonesBits rad = GetTownRadiusGroup(t, tile); - - /* Above snow? */ - int land = _settings_game.game_creation.landscape; - if (land == LT_ARCTIC && maxz > HighestSnowLine()) land = -1; - - uint bitmask = (1 << rad) + (1 << (land + 12)); + bool above_snowline = _settings_game.game_creation.landscape == LT_ARCTIC && GetTileMaxZ(tile) > HighestSnowLine(); + HouseZonesBits zone = GetTownRadiusGroup(t, tile); /* bits 0-4 are used * bits 11-15 are used @@ -2277,22 +2491,11 @@ static bool BuildTownHouse(Town *t, TileIndex tile) /* Generate a list of all possible houses that can be built. */ for (uint i = 0; i < NUM_HOUSES; i++) { - const HouseSpec *hs = HouseSpec::Get(i); - - /* Verify that the candidate house spec matches the current tile status */ - if ((~hs->building_availability & bitmask) != 0 || !hs->enabled || hs->grf_prop.override != INVALID_HOUSE_ID) continue; - - /* Don't let these counters overflow. Global counters are 32bit, there will never be that many houses. */ - if (hs->class_id != HOUSE_NO_CLASS) { - /* id_count is always <= class_count, so it doesn't need to be checked */ - if (t->cache.building_counts.class_count[hs->class_id] == UINT16_MAX) continue; - } else { - /* If the house has no class, check id_count instead */ - if (t->cache.building_counts.id_count[i] == UINT16_MAX) continue; - } + if (IsHouseTypeAllowed((HouseID)i, above_snowline, zone).Failed()) continue; + if (IsAnotherHouseTypeAllowedInTown(t, (HouseID)i).Failed()) continue; /* Without NewHouses, all houses have probability '1' */ - uint cur_prob = (_loaded_newgrf_features.has_newhouses ? hs->probability : 1); + uint cur_prob = (_loaded_newgrf_features.has_newhouses ? HouseSpec::Get(i)->probability : 1); probability_max += cur_prob; probs[num] = cur_prob; houses[num++] = (HouseID)i; @@ -2323,73 +2526,18 @@ static bool BuildTownHouse(Town *t, TileIndex tile) houses[i] = houses[num]; probs[i] = probs[num]; - const HouseSpec *hs = HouseSpec::Get(house); - - if (_loaded_newgrf_features.has_newhouses && !_generating_world && - _game_mode != GM_EDITOR && (hs->extra_flags & BUILDING_IS_HISTORICAL) != 0) { - continue; - } - - if (_cur_year < hs->min_year || _cur_year > hs->max_year) continue; + CommandCost ret = CheckCanBuildHouse(house, t); + if (ret.Failed()) continue; - /* Special houses that there can be only one of. */ - uint oneof = 0; - - if (hs->building_flags & BUILDING_IS_CHURCH) { - SetBit(oneof, TOWN_HAS_CHURCH); - } else if (hs->building_flags & BUILDING_IS_STADIUM) { - SetBit(oneof, TOWN_HAS_STADIUM); - } - - if (t->flags & oneof) continue; - - /* Make sure there is no slope? */ - bool noslope = (hs->building_flags & TILE_NOT_SLOPED) != 0; - if (noslope && slope != SLOPE_FLAT) continue; - - if (hs->building_flags & TILE_SIZE_2x2) { - if (!CheckTownBuild2x2House(&tile, t, maxz, noslope)) continue; - } else if (hs->building_flags & TILE_SIZE_2x1) { - if (!CheckTownBuild2House(&tile, t, maxz, noslope, DIAGDIR_SW)) continue; - } else if (hs->building_flags & TILE_SIZE_1x2) { - if (!CheckTownBuild2House(&tile, t, maxz, noslope, DIAGDIR_SE)) continue; - } else { - /* 1x1 house checks are already done */ - } + tile = FindPlaceForTownHouseAroundTile(tile, t, house); + if (tile == INVALID_TILE) continue; byte random_bits = Random(); - if (HasBit(hs->callback_mask, CBM_HOUSE_ALLOW_CONSTRUCTION)) { - uint16 callback_res = GetHouseCallback(CBID_HOUSE_ALLOW_CONSTRUCTION, 0, 0, house, t, tile, true, random_bits); - if (callback_res != CALLBACK_FAILED && !Convert8bitBooleanCallback(hs->grf_prop.grffile, CBID_HOUSE_ALLOW_CONSTRUCTION, callback_res)) continue; - } - - /* build the house */ - t->cache.num_houses++; - - /* Special houses that there can be only one of. */ - t->flags |= oneof; - - byte construction_counter = 0; - byte construction_stage = 0; - - if (_generating_world || _game_mode == GM_EDITOR) { - uint32 r = Random(); - - construction_stage = TOWN_HOUSE_COMPLETED; - if (Chance16(1, 7)) construction_stage = GB(r, 0, 2); - - if (construction_stage == TOWN_HOUSE_COMPLETED) { - ChangePopulation(t, hs->population); - } else { - construction_counter = GB(r, 2, 2); - } - } - - MakeTownHouse(tile, t, construction_counter, construction_stage, house, random_bits); - UpdateTownRadius(t); - UpdateTownCargoes(t, tile); + /* Check if GRF allows this house */ + if (!HouseAllowsConstruction(house, tile, t, random_bits)) continue; + DoBuildHouse(t, tile, house, random_bits); return true; } diff --git a/src/town_gui.cpp b/src/town_gui.cpp index 222549ff73..68f8eab962 100644 --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -32,6 +32,10 @@ #include "core/geometry_func.hpp" #include "genworld.h" #include "widgets/dropdown_func.h" +#include "newgrf_config.h" +#include "newgrf_house.h" +#include "date_func.h" +#include "core/random_func.hpp" #include "widgets/town_widget.h" @@ -1197,3 +1201,676 @@ void ShowFoundTownWindow() if (_game_mode != GM_EDITOR && !Company::IsValidID(_local_company)) return; AllocateWindowDescFront(&_found_town_desc, 0); } + +class GUIHouseList : public SmallVector { +protected: + SmallVector house_sets; ///< list of house sets, each item points the first house of the set in the houses array + + static int CDECL HouseSorter(const HouseID *a, const HouseID *b) + { + const HouseSpec *a_hs = HouseSpec::Get(*a); + const GRFFile *a_set = a_hs->grf_prop.grffile; + const HouseSpec *b_hs = HouseSpec::Get(*b); + const GRFFile *b_set = b_hs->grf_prop.grffile; + + int ret = (a_set != NULL) - (b_set != NULL); + if (ret == 0) { + if (a_set != NULL) { + assert_compile(sizeof(a_set->grfid) <= sizeof(int)); + ret = a_set->grfid - b_set->grfid; + if (ret == 0) ret = a_hs->grf_prop.local_id - b_hs->grf_prop.local_id; + } else { + ret = *a - *b; + } + } + return ret; + } + +public: + GUIHouseList() + { + *this->house_sets.Append() = 0; // terminator + } + + inline HouseID GetHouseAtOffset(uint house_set, uint house_offset) const + { + return *this->Get(this->house_sets[house_set] + house_offset); + } + + uint NumHouseSets() const + { + return this->house_sets.Length() - 1; // last item is a terminator + } + + uint NumHousesInHouseSet(uint house_set) const + { + assert(house_set < this->NumHouseSets()); + /* There is a terminator on the list of house sets. It's equal to the number + * of all houses. We can safely use "house_set + 1" even for the last + * house set. */ + return this->house_sets[house_set + 1] - this->house_sets[house_set]; + } + + int FindHouseSet(HouseID house) const + { + const GRFFile *house_set = HouseSpec::Get(house)->grf_prop.grffile; + for (uint i = 0; i < this->NumHouseSets(); i++) { + if (HouseSpec::Get(this->GetHouseAtOffset(i, 0))->grf_prop.grffile == house_set) return i; + } + return -1; + } + + int FindHouseOffset(uint house_set, HouseID house) const + { + assert(house_set < this->NumHouseSets()); + uint count = this->NumHousesInHouseSet(house_set); + for (uint i = 0; i < count; i++) { + if (this->GetHouseAtOffset(house_set, i) == house) return i; + } + return -1; + } + + const char *GetNameOfHouseSet(uint house_set) const + { + assert(house_set < this->NumHouseSets()); + const GRFFile *gf = HouseSpec::Get(this->GetHouseAtOffset(house_set, 0))->grf_prop.grffile; + if (gf != NULL) return GetGRFConfig(gf->grfid)->GetName(); + + static char name[DRAW_STRING_BUFFER]; + GetString(name, STR_BASIC_HOUSE_SET_NAME, lastof(name)); + return name; + } + + /** + * Notify the sortlist that the rebuild is done + * + * @note This forces a resort + */ + void Build() + { + /* collect items */ + this->Clear(); + for (HouseID house = 0; house < NUM_HOUSES; house++) { + const HouseSpec *hs = HouseSpec::Get(house); + /* is the house enabled? */ + if (!hs->enabled) continue; + /* is the house overriden? */ + if (hs->grf_prop.override != INVALID_HOUSE_ID) continue; + /* is the house allownd in current landscape? */ + HouseZones landscapes = (HouseZones)(HZ_TEMP << _settings_game.game_creation.landscape); + if (_settings_game.game_creation.landscape == LT_ARCTIC) landscapes |= HZ_SUBARTC_ABOVE; + if (!(hs->building_availability & landscapes)) continue; + /* is the house allowed at any of house zones at all? */ + if (!(hs->building_availability & HZ_ZONALL)) continue; + /* is there any year in which the house is allowed? */ + if (hs->min_year > hs->max_year) continue; + + /* add the house */ + *this->Append() = house; + } + + /* arrange items */ + QSortT(this->Begin(), this->Length(), HouseSorter); + + /* list house sets */ + this->house_sets.Clear(); + const GRFFile *last_set; + for (uint i = 0; i < this->Length(); i++) { + const HouseSpec *hs = HouseSpec::Get((*this)[i]); + /* add house set */ + if (this->house_sets.Length() == 0 || last_set != hs->grf_prop.grffile) { + last_set = hs->grf_prop.grffile; + *this->house_sets.Append() = i; + } + } + /* put a terminator on the list to make counting easier */ + *this->house_sets.Append() = this->Length(); + } +}; + +static HouseID _cur_house = INVALID_HOUSE_ID; ///< house selected in the house picker window + +/** The window used for building houses. */ +class HousePickerWindow : public PickerWindowBase { +protected: + GUIHouseList house_list; ///< list of houses and house sets + uint house_offset; ///< index of selected house + uint house_set; ///< index of selected house set + uint line_height; ///< height of a single line in the list of house sets + + void RestoreSelectedHouseIndex() + { + this->house_set = 0; + this->house_offset = 0; + + if (this->house_list.Length() == 0) { // no houses at all? + _cur_house = INVALID_HOUSE_ID; + return; + } + + if (_cur_house != INVALID_HOUSE_ID) { + int house_set = this->house_list.FindHouseSet(_cur_house); + if (house_set >= 0) { + this->house_set = house_set; + int house_offset = this->house_list.FindHouseOffset(house_set, _cur_house); + if (house_offset >= 0) { + this->house_offset = house_offset; + return; + } + } + } + _cur_house = this->house_list.GetHouseAtOffset(this->house_set, this->house_offset); + } + + /** + * Select another house. + * @param new_house_set index of the house set + * @param new_house_offset offset of the house + */ + void SelectOtherHouse(uint new_house_set, uint new_house_offset) + { + assert(new_house_set < this->house_list.NumHouseSets()); + assert(new_house_offset < this->house_list.NumHousesInHouseSet(new_house_set)); + + _cur_house = this->house_list.GetHouseAtOffset(new_house_set, new_house_offset); + this->house_set = new_house_set; + this->house_offset = new_house_offset; + + NWidgetMatrix *matrix = this->GetWidget(WID_HP_HOUSE_SELECT_MATRIX); + matrix->SetCount(this->house_list.NumHousesInHouseSet(this->house_set)); + matrix->SetClicked(this->house_offset); + this->UpdateSelectSize(); + this->SetDirty(); + } + + void UpdateSelectSize() + { + uint w = 1, h = 1; + if (_cur_house != INVALID_HOUSE_ID) { + const HouseSpec *hs = HouseSpec::Get(_cur_house); + if (hs->building_flags & BUILDING_2_TILES_X) w++; + if (hs->building_flags & BUILDING_2_TILES_Y) h++; + } + SetTileSelectSize(w, h); + } + +public: + HousePickerWindow(WindowDesc *desc, Window *w) : PickerWindowBase(desc, w) + { + this->CreateNestedTree(); + /* there is no shade box but we will shade the window if there is no house to show */ + this->shade_select = this->GetWidget(WID_HP_MAIN_PANEL_SEL); + NWidgetMatrix *matrix = this->GetWidget(WID_HP_HOUSE_SELECT_MATRIX); + matrix->SetScrollbar(this->GetScrollbar(WID_HP_HOUSE_SELECT_SCROLL)); + this->FinishInitNested(0); + + if (_cur_house != INVALID_HOUSE_ID) matrix->SetClicked(this->house_offset); // set clicked item again to make it visible + } + + ~HousePickerWindow() + { + DeleteWindowById(WC_SELECT_TOWN, 0); + } + + virtual void OnInit() + { + this->house_list.Build(); + this->RestoreSelectedHouseIndex(); + this->UpdateSelectSize(); + + /* if we have exactly one set of houses and it's not the default one then display it's name in the title bar */ + this->GetWidget(WID_HP_CAPTION)->widget_data = + (this->house_list.NumHouseSets() == 1 && HouseSpec::Get(this->house_list[0])->grf_prop.grffile != NULL) ? + STR_HOUSE_BUILD_CUSTOM_CAPTION : STR_HOUSE_BUILD_CAPTION; + + /* hide widgets if we have no houses to show */ + this->SetShaded(this->house_list.Length() == 0); + + if (this->house_list.Length() != 0) { + /* show the list of house sets if we have at least 2 items to show */ + this->GetWidget(WID_HP_HOUSE_SETS_SEL)->SetDisplayedPlane(this->house_list.NumHouseSets() > 1 ? 0 : SZSP_NONE); + /* set number of items in the list of house sets */ + this->GetWidget(WID_HP_HOUSE_SETS)->widget_data = (this->house_list.NumHouseSets() << MAT_ROW_START) | (1 << MAT_COL_START); + /* show the landscape info only in arctic climate (above/below snowline) */ + this->GetWidget(WID_HP_HOUSE_LANDSCAPE_SEL)->SetDisplayedPlane(_settings_game.game_creation.landscape == LT_ARCTIC ? 0 : SZSP_NONE); + /* update the matrix of houses */ + NWidgetMatrix *matrix = this->GetWidget(WID_HP_HOUSE_SELECT_MATRIX); + matrix->SetCount(this->house_list.NumHousesInHouseSet(this->house_set)); + matrix->SetClicked(this->house_offset); + } + } + + virtual void SetStringParameters(int widget) const + { + switch (widget) { + case WID_HP_CAPTION: + if (this->house_list.NumHouseSets() == 1) SetDParamStr(0, this->house_list.GetNameOfHouseSet(0)); + break; + + case WID_HP_HOUSE_NAME: + SetDParam(0, GetHouseName(_cur_house)); + break; + + case WID_HP_HISTORICAL_BUILDING: + SetDParam(0, HouseSpec::Get(_cur_house)->extra_flags & BUILDING_IS_HISTORICAL ? STR_HOUSE_BUILD_HISTORICAL_BUILDING : STR_EMPTY); + break; + + case WID_HP_HOUSE_POPULATION: + SetDParam(0, HouseSpec::Get(_cur_house)->population); + break; + + case WID_HP_HOUSE_ZONES: { + HouseZones zones = (HouseZones)(HouseSpec::Get(_cur_house)->building_availability & HZ_ZONALL); + for (int i = 0; i < HZB_END; i++) { + /* colour: gold(enabled)/grey(disabled) */ + SetDParam(2 * i, HasBit(zones, HZB_END - i - 1) ? STR_HOUSE_BUILD_HOUSE_ZONE_ENABLED : STR_HOUSE_BUILD_HOUSE_ZONE_DISABLED); + /* digit: 1(center)/2/3/4/5(edge) */ + SetDParam(2 * i + 1, i + 1); + } + break; + } + + case WID_HP_HOUSE_LANDSCAPE: { + StringID info = STR_HOUSE_BUILD_LANDSCAPE_ABOVE_OR_BELOW_SNOWLINE; + switch (HouseSpec::Get(_cur_house)->building_availability & (HZ_SUBARTC_ABOVE | HZ_SUBARTC_BELOW)) { + case HZ_SUBARTC_ABOVE: info = STR_HOUSE_BUILD_LANDSCAPE_ONLY_ABOVE_SNOWLINE; break; + case HZ_SUBARTC_BELOW: info = STR_HOUSE_BUILD_LANDSCAPE_ONLY_BELOW_SNOWLINE; break; + default: break; + } + SetDParam(0, info); + break; + } + + case WID_HP_HOUSE_YEARS: { + const HouseSpec *hs = HouseSpec::Get(_cur_house); + SetDParam(0, hs->min_year <= _cur_year ? STR_HOUSE_BUILD_YEARS_GOOD_YEAR : STR_HOUSE_BUILD_YEARS_BAD_YEAR); + SetDParam(1, hs->min_year); + SetDParam(2, hs->max_year >= _cur_year ? STR_HOUSE_BUILD_YEARS_GOOD_YEAR : STR_HOUSE_BUILD_YEARS_BAD_YEAR); + SetDParam(3, hs->max_year); + break; + } + + case WID_HP_HOUSE_ACCEPTANCE: { + static char buff[DRAW_STRING_BUFFER] = ""; + char *str = buff; + CargoArray cargo; + uint32 dummy = 0; + AddAcceptedHouseCargo(_cur_house, INVALID_TILE, cargo, &dummy); + for (uint i = 0; i < NUM_CARGO; i++) { + if (cargo[i] == 0) continue; + /* If the accepted value is less than 8, show it in 1/8:ths */ + SetDParam(0, cargo[i] < 8 ? STR_HOUSE_BUILD_CARGO_VALUE_EIGHTS : STR_HOUSE_BUILD_CARGO_VALUE_JUST_NAME); + SetDParam(1, cargo[i]); + SetDParam(2, CargoSpec::Get(i)->name); + str = GetString(str, str == buff ? STR_HOUSE_BUILD_CARGO_FIRST : STR_HOUSE_BUILD_CARGO_SEPARATED, lastof(buff)); + } + if (str == buff) GetString(buff, STR_JUST_NOTHING, lastof(buff)); + SetDParamStr(0, buff); + break; + } + + case WID_HP_HOUSE_SUPPLY: { + CargoArray cargo; + AddProducedHouseCargo(_cur_house, INVALID_TILE, cargo); + uint32 cargo_mask = 0; + for (uint i = 0; i < NUM_CARGO; i++) if (cargo[i] != 0) SetBit(cargo_mask, i); + SetDParam(0, cargo_mask); + break; + } + + default: break; + } + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + switch (widget) { + case WID_HP_HOUSE_SETS: { + uint max_w = 0; + for (uint i = 0; i < this->house_list.NumHouseSets(); i++) { + max_w = max(max_w, GetStringBoundingBox(this->house_list.GetNameOfHouseSet(i)).width); + } + size->width = max(size->width, max_w + padding.width); + this->line_height = FONT_HEIGHT_NORMAL + WD_MATRIX_TOP + WD_MATRIX_BOTTOM; + size->height = this->house_list.NumHouseSets() * this->line_height; + break; + } + + case WID_HP_HOUSE_NAME: + size->width = 120; // we do not want this window to get too wide, better clip + break; + + case WID_HP_HISTORICAL_BUILDING: + size->width = max(size->width, GetStringBoundingBox(STR_HOUSE_BUILD_HISTORICAL_BUILDING).width + padding.width); + break; + + case WID_HP_HOUSE_POPULATION: + SetDParam(0, 0); + /* max popultion is 255 - 3 digits */ + size->width = max(size->width, GetStringBoundingBox(STR_HOUSE_BUILD_HOUSE_POPULATION).width + 3 * GetDigitWidth() + padding.width); + break; + + case WID_HP_HOUSE_ZONES: { + for (int i = 0; i < HZB_END; i++) { + SetDParam(2 * i, STR_HOUSE_BUILD_HOUSE_ZONE_ENABLED); // colour + SetDParam(2 * i + 1, i + 1); // digit: 1(center)/2/3/4/5(edge) + } + size->width = max(size->width, GetStringBoundingBox(STR_HOUSE_BUILD_HOUSE_ZONES).width + padding.width); + break; + } + + case WID_HP_HOUSE_LANDSCAPE: { + SetDParam(0, STR_HOUSE_BUILD_LANDSCAPE_ABOVE_OR_BELOW_SNOWLINE); + Dimension dim = GetStringBoundingBox(STR_HOUSE_BUILD_LANDSCAPE); + SetDParam(0, STR_HOUSE_BUILD_LANDSCAPE_ONLY_ABOVE_SNOWLINE); + dim = maxdim(dim, GetStringBoundingBox(STR_HOUSE_BUILD_LANDSCAPE)); + SetDParam(0, STR_HOUSE_BUILD_LANDSCAPE_ONLY_BELOW_SNOWLINE); + dim = maxdim(dim, GetStringBoundingBox(STR_HOUSE_BUILD_LANDSCAPE)); + dim.width += padding.width; + dim.height += padding.height; + *size = maxdim(*size, dim); + break; + } + + case WID_HP_HOUSE_YEARS: { + SetDParam(0, STR_HOUSE_BUILD_YEARS_GOOD_YEAR); + SetDParam(1, 0); + SetDParam(2, STR_HOUSE_BUILD_YEARS_GOOD_YEAR); + SetDParam(3, 0); + Dimension dim = GetStringBoundingBox(STR_HOUSE_BUILD_YEARS); + dim.width += 14 * GetDigitWidth() + padding.width; // space for about 16 digits (14 + two zeros) should be enough, don't make the window too wide + dim.height += padding.height; + *size = maxdim(*size, dim); + break; + } + + case WID_HP_HOUSE_SELECT_MATRIX: + resize->height = 1; // don't snap to rows of this matrix + break; + + /* these texts can be long, better clip */ + case WID_HP_HOUSE_ACCEPTANCE: + case WID_HP_HOUSE_SUPPLY: + size->width = 0; + break; + + default: break; + } + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + switch (GB(widget, 0, 16)) { + case WID_HP_HOUSE_SETS: { + int y = r.top + WD_MATRIX_TOP; + for (uint i = 0; i < this->house_list.NumHouseSets(); i++) { + SetDParamStr(0, this->house_list.GetNameOfHouseSet(i)); + DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, y, STR_JUST_RAW_STRING, i == this->house_set ? TC_WHITE : TC_BLACK); + y += this->line_height; + } + break; + } + + case WID_HP_HOUSE_PREVIEW: + DrawHouseImage(_cur_house, r.left, r.top, r.right, r.bottom); + break; + + case WID_HP_HOUSE_SELECT: { + HouseID house = this->house_list.GetHouseAtOffset(this->house_set, GB(widget, 16, 16)); + int lowered = (house == _cur_house) ? 1 : 0; + DrawHouseImage(house, + r.left + WD_MATRIX_LEFT + lowered, r.top + WD_MATRIX_TOP + lowered, + r.right - WD_MATRIX_RIGHT + lowered, r.bottom - WD_MATRIX_BOTTOM + lowered); + const HouseSpec *hs = HouseSpec::Get(house); + /* disabled? */ + if (_cur_year < hs->min_year || _cur_year > hs->max_year) { + GfxFillRect(r.left + 1, r.top + 1, r.right - 1, r.bottom - 1, PC_BLACK, FILLRECT_CHECKER); + } + break; + } + } + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch (GB(widget, 0, 16)) { + case WID_HP_HOUSE_SETS: { + uint index = (uint)(pt.y - this->GetWidget(widget)->pos_y) / this->line_height; + if (index < this->house_list.NumHouseSets() && index != this->house_set) this->SelectOtherHouse(index, 0); + break; + } + + case WID_HP_HOUSE_SELECT: + this->SelectOtherHouse(this->house_set, GB(widget, 16, 16)); + break; + } + } +}; + +static const NWidgetPart _nested_house_picker_widgets[] = { + /* TOP */ + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_HP_CAPTION), SetDataTip(STR_HOUSE_BUILD_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), + NWidget(NWID_SELECTION, COLOUR_DARK_GREEN, WID_HP_MAIN_PANEL_SEL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_HP_HOUSE_SELECT_SCROLL), + /* MIDDLE */ + NWidget(NWID_HORIZONTAL), SetPIP(5, 0, 0), + /* LEFT */ + NWidget(NWID_VERTICAL), SetPIP(5, 2, 2), + /* LIST OF HOUSE SETS */ + NWidget(NWID_SELECTION, COLOUR_DARK_GREEN, WID_HP_HOUSE_SETS_SEL), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_MATRIX, COLOUR_GREY, WID_HP_HOUSE_SETS), SetMinimalSize(0, 60), SetFill(1, 0), SetResize(0, 0), + SetMatrixDataTip(1, 1, STR_HOUSE_BUILD_HOUSESET_LIST_TOOLTIP), + EndContainer(), + EndContainer(), + /* HOUSE PICTURE AND LABEL */ + NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_PREVIEW), SetFill(1, 1), SetResize(0, 1), SetMinimalSize(2 * TILE_PIXELS, 142), + NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_HP_HOUSE_NAME), SetDataTip(STR_HOUSE_BUILD_HOUSE_NAME, STR_NULL), SetMinimalSize(120, 0), + NWidget(WWT_LABEL, COLOUR_DARK_GREEN, WID_HP_HISTORICAL_BUILDING), SetDataTip(STR_JUST_STRING, STR_NULL), + /* HOUSE INFOS (SHORT TEXTS) */ + NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_POPULATION), SetDataTip(STR_HOUSE_BUILD_HOUSE_POPULATION, STR_NULL), SetPadding(5, 0, 0, 0), + NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_ZONES), SetDataTip(STR_HOUSE_BUILD_HOUSE_ZONES, STR_NULL), + NWidget(NWID_SELECTION, COLOUR_DARK_GREEN, WID_HP_HOUSE_LANDSCAPE_SEL), + NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_LANDSCAPE), SetDataTip(STR_HOUSE_BUILD_LANDSCAPE, STR_NULL), + EndContainer(), + NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_YEARS), SetDataTip(STR_HOUSE_BUILD_YEARS, STR_NULL), + EndContainer(), + /* RIGHT: MATRIX OF HOUSES */ + NWidget(NWID_MATRIX, COLOUR_DARK_GREEN, WID_HP_HOUSE_SELECT_MATRIX), SetPIP(0, 2, 0), SetPadding(2, 2, 2, 2), SetScrollbar(WID_HP_HOUSE_SELECT_SCROLL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_HP_HOUSE_SELECT), SetMinimalSize(64, 64), SetFill(0, 0), SetResize(0, 0), + SetDataTip(0x0, STR_HOUSE_BUILD_SELECT_HOUSE_TOOLTIP), SetScrollbar(WID_HP_HOUSE_SELECT_SCROLL), + EndContainer(), + EndContainer(), + NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_HP_HOUSE_SELECT_SCROLL), + EndContainer(), + /* BOTTOM */ + NWidget(NWID_HORIZONTAL), SetPIP(5, 2, 0), + /* HOUSE INFOS (LONG TEXTS) */ + NWidget(NWID_VERTICAL), SetPIP(0, 2, 5), + NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_ACCEPTANCE), SetDataTip(STR_HOUSE_BUILD_ACCEPTED_CARGO, STR_NULL), SetFill(1, 0), SetResize(1, 0), + NWidget(WWT_TEXT, COLOUR_DARK_GREEN, WID_HP_HOUSE_SUPPLY), SetDataTip(STR_HOUSE_BUILD_SUPPLIED_CARGO, STR_NULL), SetFill(1, 0), SetResize(1, 0), + EndContainer(), + /* RESIZE BOX */ + NWidget(NWID_VERTICAL), + NWidget(NWID_SPACER), SetFill(0, 1), + NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), + EndContainer(), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _house_picker_desc( + WDP_AUTO, "build_house", 0, 0, + WC_BUILD_HOUSE, WC_BUILD_TOOLBAR, + WDF_CONSTRUCTION, + _nested_house_picker_widgets, lengthof(_nested_house_picker_widgets) +); + +/** + * Show our house picker. + * @param parent The toolbar window we're associated with. + */ +void ShowBuildHousePicker(Window *parent) +{ + new HousePickerWindow(&_house_picker_desc, parent); +} + + +/** + * Window for selecting towns to build a house in. + */ +struct SelectTownWindow : Window { + TownList towns; ///< list of towns + CommandContainer cmd; ///< command to build the house (CMD_BUILD_HOUSE) + Scrollbar *vscroll; ///< scrollbar for the town list + + SelectTownWindow(WindowDesc *desc, const TownList &towns, const CommandContainer &cmd) : Window(desc), towns(towns), cmd(cmd) + { + this->CreateNestedTree(); + this->vscroll = this->GetScrollbar(WID_ST_SCROLLBAR); + this->vscroll->SetCount(this->towns.Length()); + this->FinishInitNested(); + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + if (widget != WID_ST_PANEL) return; + + /* Determine the widest string */ + Dimension d = { 0, 0 }; + for (uint i = 0; i < this->towns.Length(); i++) { + SetDParam(0, this->towns[i]); + d = maxdim(d, GetStringBoundingBox(STR_SELECT_TOWN_LIST_ITEM)); + } + + resize->height = d.height; + d.height *= 5; + d.width += WD_FRAMERECT_RIGHT + WD_FRAMERECT_LEFT; + d.height += WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM; + *size = d; + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + if (widget != WID_ST_PANEL) return; + + uint y = r.top + WD_FRAMERECT_TOP; + uint end = min(this->vscroll->GetCount(), this->vscroll->GetPosition() + this->vscroll->GetCapacity()); + for (uint i = this->vscroll->GetPosition(); i < end; i++) { + SetDParam(0, this->towns[i]); + DrawString(r.left + WD_FRAMERECT_LEFT, r.right - WD_FRAMERECT_RIGHT, y, STR_SELECT_TOWN_LIST_ITEM); + y += this->resize.step_height; + } + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + if (widget != WID_ST_PANEL) return; + + uint pos = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_ST_PANEL, WD_FRAMERECT_TOP); + if (pos >= this->towns.Length()) return; + + /* Place a house */ + SB(this->cmd.p1, 16, 16, this->towns[pos]); + DoCommandP(&this->cmd); + + /* Close the window */ + delete this; + } + + virtual void OnResize() + { + this->vscroll->SetCapacityFromWidget(this, WID_ST_PANEL, WD_FRAMERECT_TOP + WD_FRAMERECT_BOTTOM); + } +}; + +static const NWidgetPart _nested_select_town_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_DARK_GREEN), + NWidget(WWT_CAPTION, COLOUR_DARK_GREEN, WID_ST_CAPTION), SetDataTip(STR_SELECT_TOWN_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + NWidget(WWT_DEFSIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), + NWidget(NWID_HORIZONTAL), + NWidget(WWT_PANEL, COLOUR_DARK_GREEN, WID_ST_PANEL), SetResize(1, 0), SetScrollbar(WID_ST_SCROLLBAR), EndContainer(), + NWidget(NWID_VERTICAL), + NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_ST_SCROLLBAR), + NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN), + EndContainer(), + EndContainer(), +}; + +static WindowDesc _select_town_desc( + WDP_AUTO, "select_town", 100, 0, + WC_SELECT_TOWN, WC_NONE, + WDF_CONSTRUCTION, + _nested_select_town_widgets, lengthof(_nested_select_town_widgets) +); + +static void ShowSelectTownWindow(const TownList &towns, const CommandContainer &cmd) +{ + DeleteWindowByClass(WC_SELECT_TOWN); + new SelectTownWindow(&_select_town_desc, towns, cmd); +} + + +void PlaceProc_House(TileIndex tile) +{ + if (_town_pool.items == 0) { + ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HOUSE_HERE, STR_ERROR_MUST_FOUND_TOWN_FIRST, WL_INFO); + return; + } + + DeleteWindowById(WC_SELECT_TOWN, 0); + + if (_cur_house == INVALID_HOUSE_ID) return; + + /* build a list of towns to join to */ + TownList towns; + HouseZones house_zones = HouseSpec::Get(_cur_house)->building_availability & HZ_ZONALL; + uint best_dist = UINT_MAX; + int best_zone = (int)HZB_BEGIN - 1; + const Town *t; + FOR_ALL_TOWNS(t) { + HouseZonesBits town_zone = TryGetTownRadiusGroup(t, tile); + if (HasBit(house_zones, town_zone)) { + /* If CTRL is NOT pressed keep only single town on the list, the best one. + * Otherwise add all towns to the list so they can be shown to the player. */ + if (!_ctrl_pressed) { + if ((int)town_zone < best_zone) continue; + uint dist = DistanceSquare(tile, t->xy); + if (dist >= best_dist) continue; + best_dist = dist; + best_zone = town_zone; + towns.Clear(); + } + *towns.Append() = t->index; + } + } + + if (towns.Length() == 0) { + ShowErrorMessage(STR_ERROR_CAN_T_BUILD_HOUSE_HERE, STR_ERROR_BUILDING_NOT_ALLOWED_IN_THIS_TOWN_ZONE, WL_INFO); + return; + } + + CommandContainer cmd = { + tile, + _cur_house, // p1 - house type and town index (town not yet set) + InteractiveRandom(), // p2 - random bits for the house + CMD_BUILD_HOUSE | CMD_MSG(STR_ERROR_CAN_T_BUILD_HOUSE_HERE), + CcPlaySound1E, + "" + }; + + if (!_ctrl_pressed) { + SB(cmd.p1, 16, 16, towns[0]); // set the town, it's alone on the list + DoCommandP(&cmd); + } else { + if (!_settings_client.gui.persistent_buildingtools) DeleteWindowById(WC_BUILD_HOUSE, 0); + ShowSelectTownWindow(towns, cmd); + } +} diff --git a/src/town_type.h b/src/town_type.h index 39deb61677..f95a5d0f74 100644 --- a/src/town_type.h +++ b/src/town_type.h @@ -13,10 +13,13 @@ #define TOWN_TYPE_H #include "core/enum_type.hpp" +#include "core/smallvec_type.hpp" typedef uint16 TownID; struct Town; +typedef SmallVector TownList; + /** Supported initial town sizes */ enum TownSize { TSZ_SMALL, ///< Small town. diff --git a/src/widgets/terraform_widget.h b/src/widgets/terraform_widget.h index 7f8a4c4d1b..67c9f09318 100644 --- a/src/widgets/terraform_widget.h +++ b/src/widgets/terraform_widget.h @@ -39,6 +39,7 @@ enum EditorTerraformToolbarWidgets { WID_ETT_PLACE_ROCKS, ///< Place rocks button. WID_ETT_PLACE_DESERT, ///< Place desert button (in tropical climate). WID_ETT_PLACE_OBJECT, ///< Place transmitter button. + WID_ETT_PLACE_HOUSE, ///< Place house button. WID_ETT_BUTTONS_END, ///< End of pushable buttons. WID_ETT_INCREASE_SIZE = WID_ETT_BUTTONS_END, ///< Upwards arrow button to increase terraforming size. WID_ETT_DECREASE_SIZE, ///< Downwards arrow button to decrease terraforming size. diff --git a/src/widgets/town_widget.h b/src/widgets/town_widget.h index 4f5443c36e..59412ee17d 100644 --- a/src/widgets/town_widget.h +++ b/src/widgets/town_widget.h @@ -62,4 +62,32 @@ enum TownFoundingWidgets { WID_TF_LAYOUT_RANDOM, ///< Selection for a randomly chosen town layout. }; +/** Widgets of the #HousePickerWindow class. */ +enum HousePickerWidgets { + WID_HP_CAPTION, + WID_HP_MAIN_PANEL_SEL, ///< Selection widget to show/hide the main panel. + WID_HP_HOUSE_SETS_SEL, ///< Selection widget to show/hide the list of house sets. + WID_HP_HOUSE_SETS, ///< List of available house sets. + WID_HP_HOUSE_SELECT_MATRIX, ///< Matrix with houses to select. + WID_HP_HOUSE_SELECT_SCROLL, ///< Scrollbar associated with the house matrix. + WID_HP_HOUSE_SELECT, ///< Panels with house images in the house matrix. + WID_HP_HOUSE_PREVIEW, ///< House preview panel. + WID_HP_HOUSE_NAME, ///< House name display. + WID_HP_HISTORICAL_BUILDING, ///< "Historical building" label. + WID_HP_HOUSE_POPULATION, ///< House population display. + WID_HP_HOUSE_ZONES, ///< House zones display. + WID_HP_HOUSE_LANDSCAPE, ///< Information about house availability against the landscape. + WID_HP_HOUSE_LANDSCAPE_SEL, ///< Selection widget to show/hide the landscape info. + WID_HP_HOUSE_YEARS, ///< Years display. + WID_HP_HOUSE_ACCEPTANCE, ///< Cargo accepted. + WID_HP_HOUSE_SUPPLY, ///< Cargo supplied. +}; + +/** Widgets of the #SelectTownWindow class. */ +enum SelectTownWidgets { + WID_ST_CAPTION, ///< Caption of the window. + WID_ST_PANEL, ///< Main panel. + WID_ST_SCROLLBAR, ///< Scrollbar of the panel. +}; + #endif /* WIDGETS_TOWN_WIDGET_H */ diff --git a/src/window_type.h b/src/window_type.h index 809e81d485..82bd99b128 100644 --- a/src/window_type.h +++ b/src/window_type.h @@ -236,6 +236,12 @@ enum WindowClass { */ WC_SELECT_STATION, + /** + * Select town (when placing a house); %Window numbers: + * - 0 = #SelectTownWidgets + */ + WC_SELECT_TOWN, + /** * News window; %Window numbers: * - 0 = #NewsWidgets @@ -370,6 +376,12 @@ enum WindowClass { */ WC_BUILD_OBJECT, + /** + * Build house; %Window numbers: + * - 0 = #BuildHouseWidgets + */ + WC_BUILD_HOUSE, + /** * Build vehicle; %Window numbers: * - #VehicleType = #BuildVehicleWidgets