maedhros@6332: /* $Id$ */ maedhros@6332: rubidium@9111: /** @file newgrf_house.cpp Implementation of NewGRF houses. */ maedhros@6332: maedhros@6332: #include "stdafx.h" maedhros@6332: #include "openttd.h" maedhros@6332: #include "variables.h" maedhros@6332: #include "debug.h" rubidium@8225: #include "viewport_func.h" maedhros@6343: #include "landscape.h" maedhros@6332: #include "town.h" maedhros@6332: #include "town_map.h" maedhros@6332: #include "sprite.h" maedhros@6332: #include "newgrf.h" maedhros@6332: #include "newgrf_house.h" maedhros@6332: #include "newgrf_spritegroup.h" maedhros@6332: #include "newgrf_callbacks.h" maedhros@6332: #include "newgrf_town.h" maedhros@6332: #include "newgrf_sound.h" belugas@6629: #include "newgrf_commons.h" belugas@7849: #include "transparency.h" rubidium@8131: #include "functions.h" rubidium@10208: #include "company_func.h" rubidium@9005: #include "animated_tile_func.h" rubidium@9006: #include "date_func.h" rubidium@10208: #include "company_base.h" maedhros@6332: rubidium@8264: #include "table/strings.h" rubidium@8264: #include "table/sprites.h" rubidium@8264: #include "table/town_land.h" rubidium@8264: maedhros@6332: static BuildingCounts _building_counts; maedhros@6332: static HouseClassMapping _class_mapping[HOUSE_CLASS_MAX]; maedhros@6332: belugas@6629: HouseOverrideManager _house_mngr(NEW_HOUSE_OFFSET, HOUSE_MAX, INVALID_HOUSE_ID); maedhros@6332: skidd13@8707: /** skidd13@8707: * Check and update town and house values. skidd13@8707: * skidd13@8707: * Checked are the HouseIDs. Updated are the skidd13@8707: * town population the number of houses per skidd13@8707: * town, the town radius and the max passengers skidd13@8707: * of the town. skidd13@8707: */ skidd13@8707: void UpdateHousesAndTowns() maedhros@6332: { glx@8291: Town *town; belugas@7465: InitializeBuildingCounts(); belugas@7465: skidd13@8707: /* Reset town population and num_houses */ skidd13@8707: FOR_ALL_TOWNS(town) { skidd13@8707: town->population = 0; skidd13@8707: town->num_houses = 0; skidd13@8707: } glx@8291: maedhros@6332: for (TileIndex t = 0; t < MapSize(); t++) { maedhros@6332: HouseID house_id; maedhros@6332: maedhros@6332: if (!IsTileType(t, MP_HOUSE)) continue; maedhros@6332: maedhros@6332: house_id = GetHouseType(t); maedhros@6332: if (!GetHouseSpecs(house_id)->enabled && house_id >= NEW_HOUSE_OFFSET) { maedhros@6332: /* The specs for this type of house are not available any more, so maedhros@6332: * replace it with the substitute original house type. */ belugas@7465: house_id = _house_mngr.GetSubstituteID(house_id); belugas@7465: SetHouseType(t, house_id); maedhros@6332: } skidd13@8707: glx@8291: town = GetTownByTile(t); glx@8291: IncreaseBuildingCount(town, house_id); glx@8291: if (IsHouseCompleted(t)) town->population += GetHouseSpecs(house_id)->population; skidd13@8707: rubidium@9657: /* Increase the number of houses for every house, but only once. */ rubidium@9657: if (GetHouseNorthPart(house_id) == 0) town->num_houses++; skidd13@8707: } skidd13@8707: skidd13@8707: /* Update the population and num_house dependant values */ skidd13@8707: FOR_ALL_TOWNS(town) { skidd13@8707: UpdateTownRadius(town); skidd13@8707: UpdateTownMaxPass(town); maedhros@6332: } maedhros@6332: } maedhros@6332: maedhros@6332: HouseClassID AllocateHouseClassID(byte grf_class_id, uint32 grfid) maedhros@6332: { maedhros@6332: /* Start from 1 because 0 means that no class has been assigned. */ maedhros@6332: for (int i = 1; i != lengthof(_class_mapping); i++) { maedhros@6332: HouseClassMapping *map = &_class_mapping[i]; maedhros@6332: maedhros@6332: if (map->class_id == grf_class_id && map->grfid == grfid) return (HouseClassID)i; maedhros@6332: maedhros@6332: if (map->class_id == 0 && map->grfid == 0) { maedhros@6332: map->class_id = grf_class_id; maedhros@6332: map->grfid = grfid; maedhros@6332: return (HouseClassID)i; maedhros@6332: } maedhros@6332: } maedhros@6332: return HOUSE_NO_CLASS; maedhros@6332: } maedhros@6332: maedhros@6332: void InitializeBuildingCounts() maedhros@6332: { maedhros@6332: memset(&_building_counts, 0, sizeof(_building_counts)); maedhros@6332: } maedhros@6332: maedhros@6332: /** maedhros@6332: * IncreaseBuildingCount() maedhros@6332: * Increase the count of a building when it has been added by a town. maedhros@6332: * @param t The town that the building is being built in maedhros@6332: * @param house_id The id of the house being added maedhros@6332: */ maedhros@6332: void IncreaseBuildingCount(Town *t, HouseID house_id) maedhros@6332: { maedhros@6332: HouseClassID class_id = GetHouseSpecs(house_id)->class_id; maedhros@6332: belugas@6914: if (!_loaded_newgrf_features.has_newhouses) return; maedhros@6332: maedhros@6332: /* If there are 255 buildings of this type in this town, there are also maedhros@6332: * at least that many houses of the same class in the town, and maedhros@6332: * therefore on the map as well. */ maedhros@6332: if (t->building_counts.id_count[house_id] == 255) return; maedhros@6332: maedhros@6332: t->building_counts.id_count[house_id]++; maedhros@6332: if (_building_counts.id_count[house_id] < 255) _building_counts.id_count[house_id]++; maedhros@6332: maedhros@6332: /* Similarly, if there are 255 houses of this class in this town, there maedhros@6332: * must be at least that number on the map too. */ maedhros@6332: if (class_id == HOUSE_NO_CLASS || t->building_counts.class_count[class_id] == 255) return; maedhros@6332: maedhros@6332: t->building_counts.class_count[class_id]++; maedhros@6332: if (_building_counts.class_count[class_id] < 255) _building_counts.class_count[class_id]++; maedhros@6332: } maedhros@6332: maedhros@6332: /** maedhros@6332: * DecreaseBuildingCount() maedhros@6332: * Decrease the number of a building when it is deleted. maedhros@6332: * @param t The town that the building was built in maedhros@6332: * @param house_id The id of the house being removed maedhros@6332: */ maedhros@6332: void DecreaseBuildingCount(Town *t, HouseID house_id) maedhros@6332: { maedhros@6332: HouseClassID class_id = GetHouseSpecs(house_id)->class_id; maedhros@6332: belugas@6914: if (!_loaded_newgrf_features.has_newhouses) return; maedhros@6332: maedhros@6332: if (t->building_counts.id_count[house_id] > 0) t->building_counts.id_count[house_id]--; maedhros@6332: if (_building_counts.id_count[house_id] > 0) _building_counts.id_count[house_id]--; maedhros@6332: maedhros@6332: if (class_id == HOUSE_NO_CLASS) return; maedhros@6332: maedhros@6332: if (t->building_counts.class_count[class_id] > 0) t->building_counts.class_count[class_id]--; maedhros@6332: if (_building_counts.class_count[class_id] > 0) _building_counts.class_count[class_id]--; maedhros@6332: } maedhros@6332: maedhros@6332: static uint32 HouseGetRandomBits(const ResolverObject *object) maedhros@6332: { maedhros@6332: const TileIndex tile = object->u.house.tile; maedhros@6332: return (tile == INVALID_TILE || !IsTileType(tile, MP_HOUSE)) ? 0 : GetHouseRandomBits(tile); maedhros@6332: } maedhros@6332: maedhros@6332: static uint32 HouseGetTriggers(const ResolverObject *object) maedhros@6332: { maedhros@6332: const TileIndex tile = object->u.house.tile; maedhros@6332: return (tile == INVALID_TILE || !IsTileType(tile, MP_HOUSE)) ? 0 : GetHouseTriggers(tile); maedhros@6332: } maedhros@6332: maedhros@6332: static void HouseSetTriggers(const ResolverObject *object, int triggers) maedhros@6332: { maedhros@6332: const TileIndex tile = object->u.house.tile; maedhros@6332: if (IsTileType(tile, MP_HOUSE)) SetHouseTriggers(tile, triggers); maedhros@6332: } maedhros@6332: maedhros@6332: static uint32 GetNumHouses(HouseID house_id, const Town *town) maedhros@6332: { maedhros@6332: uint8 map_id_count, town_id_count, map_class_count, town_class_count; maedhros@6332: HouseClassID class_id = GetHouseSpecs(house_id)->class_id; maedhros@6332: maedhros@6332: map_id_count = _building_counts.id_count[house_id]; maedhros@6332: map_class_count = _building_counts.class_count[class_id]; maedhros@6332: town_id_count = town->building_counts.id_count[house_id]; maedhros@6332: town_class_count = town->building_counts.class_count[class_id]; maedhros@6332: maedhros@6332: return map_class_count << 24 | town_class_count << 16 | map_id_count << 8 | town_id_count; maedhros@6332: } maedhros@6332: belugas@6827: uint32 GetNearbyTileInformation(byte parameter, TileIndex tile) belugas@6827: { belugas@6827: tile = GetNearbyTile(parameter, tile); frosch@8458: return GetNearbyTileInformation(tile); belugas@6827: } belugas@6827: frosch@9594: /** Structure with user-data for SearchNearbyHouseXXX - functions */ frosch@9594: typedef struct { frosch@9594: const HouseSpec *hs; ///< Specs of the house, that startet the search frosch@9594: TileIndex north_tile; ///< Northern tile of the house. frosch@9594: } SearchNearbyHouseData; frosch@9594: belugas@9574: /** Callback function to search a house by its HouseID belugas@9574: * @param tile TileIndex to be examined frosch@9594: * @param user_data SearchNearbyHouseData belugas@9574: * @return true or false, if found or not belugas@9574: */ frosch@9592: static bool SearchNearbyHouseID(TileIndex tile, void *user_data) belugas@9574: { belugas@9574: if (IsTileType(tile, MP_HOUSE)) { frosch@9594: HouseID house = GetHouseType(tile); // tile been examined frosch@9594: const HouseSpec *hs = GetHouseSpecs(house); belugas@9574: if (hs->grffile != NULL) { // must be one from a grf file frosch@9594: SearchNearbyHouseData *nbhd = (SearchNearbyHouseData *)user_data; frosch@9594: frosch@9594: TileIndex north_tile = tile + GetHouseNorthPart(house); // modifies 'house'! frosch@9594: if (north_tile == nbhd->north_tile) return false; // Always ignore origin house frosch@9594: frosch@9594: return hs->local_id == nbhd->hs->local_id && // same local id as the one requested frosch@9594: hs->grffile->grfid == nbhd->hs->grffile->grfid; // from the same grf belugas@9574: } belugas@9574: } belugas@9574: return false; belugas@9574: } belugas@9574: belugas@9574: /** Callback function to search a house by its classID belugas@9574: * @param tile TileIndex to be examined frosch@9594: * @param user_data SearchNearbyHouseData belugas@9574: * @return true or false, if found or not belugas@9574: */ frosch@9592: static bool SearchNearbyHouseClass(TileIndex tile, void *user_data) belugas@9574: { belugas@9574: if (IsTileType(tile, MP_HOUSE)) { frosch@9594: HouseID house = GetHouseType(tile); // tile been examined frosch@9594: const HouseSpec *hs = GetHouseSpecs(house); belugas@9574: if (hs->grffile != NULL) { // must be one from a grf file frosch@9594: SearchNearbyHouseData *nbhd = (SearchNearbyHouseData *)user_data; frosch@9594: frosch@9594: TileIndex north_tile = tile + GetHouseNorthPart(house); // modifies 'house'! frosch@9594: if (north_tile == nbhd->north_tile) return false; // Always ignore origin house frosch@9594: frosch@9594: return hs->class_id == nbhd->hs->class_id && // same classid as the one requested frosch@9594: hs->grffile->grfid == nbhd->hs->grffile->grfid; // from the same grf belugas@9574: } belugas@9574: } belugas@9574: return false; belugas@9574: } belugas@9574: belugas@9574: /** Callback function to search a house by its grfID belugas@9574: * @param tile TileIndex to be examined frosch@9594: * @param user_data SearchNearbyHouseData belugas@9574: * @return true or false, if found or not belugas@9574: */ frosch@9592: static bool SearchNearbyHouseGRFID(TileIndex tile, void *user_data) belugas@9574: { belugas@9574: if (IsTileType(tile, MP_HOUSE)) { frosch@9594: HouseID house = GetHouseType(tile); // tile been examined frosch@9594: const HouseSpec *hs = GetHouseSpecs(house); belugas@9574: if (hs->grffile != NULL) { // must be one from a grf file frosch@9594: SearchNearbyHouseData *nbhd = (SearchNearbyHouseData *)user_data; frosch@9594: frosch@9594: TileIndex north_tile = tile + GetHouseNorthPart(house); // modifies 'house'! frosch@9594: if (north_tile == nbhd->north_tile) return false; // Always ignore origin house frosch@9594: frosch@9594: return hs->grffile->grfid == nbhd->hs->grffile->grfid; // from the same grf belugas@9574: } belugas@9574: } belugas@9574: return false; belugas@9574: } belugas@9574: belugas@9574: /** This function will activate a search around a central tile, looking for some houses belugas@9574: * that fit the requested characteristics belugas@9574: * @param parameter that is given by the callback. belugas@9574: * bits 0..6 radius of the search belugas@9574: * bits 7..8 search type i.e.: 0 = houseID/ 1 = classID/ 2 = grfID belugas@9574: * @param tile TileIndex from which to start the search frosch@9592: * @param house the HouseID that is associated to the house, the callback is called for frosch@9592: * @return the Manhattan distance from the center tile, if any, and 0 if failure frosch@9592: */ frosch@9592: static uint32 GetDistanceFromNearbyHouse(uint8 parameter, TileIndex tile, HouseID house) belugas@9574: { belugas@9574: static TestTileOnSearchProc * const search_procs[3] = { belugas@9574: SearchNearbyHouseID, belugas@9574: SearchNearbyHouseClass, belugas@9574: SearchNearbyHouseGRFID, belugas@9574: }; belugas@9574: TileIndex found_tile = tile; belugas@9574: uint8 searchtype = GB(parameter, 6, 2); belugas@9574: uint8 searchradius = GB(parameter, 0, 6); belugas@9574: if (searchtype >= lengthof(search_procs)) return 0; // do not run on ill-defined code frosch@9589: if (searchradius < 1) return 0; // do not use a too low radius belugas@9574: frosch@9594: SearchNearbyHouseData nbhd; frosch@9594: nbhd.hs = GetHouseSpecs(house); frosch@9594: nbhd.north_tile = tile + GetHouseNorthPart(house); // modifies 'house'! frosch@9594: belugas@9574: /* Use a pointer for the tile to start the search. Will be required for calculating the distance*/ frosch@9594: if (CircularTileSearch(&found_tile, 2 * searchradius + 1, search_procs[searchtype], &nbhd)) { belugas@9574: return DistanceManhattan(found_tile, tile); belugas@9574: } belugas@9574: return 0; belugas@9574: } belugas@9574: maedhros@6332: /** maedhros@6332: * HouseGetVariable(): maedhros@6332: * maedhros@6332: * Used by the resolver to get values for feature 07 deterministic spritegroups. maedhros@6332: */ maedhros@6332: static uint32 HouseGetVariable(const ResolverObject *object, byte variable, byte parameter, bool *available) maedhros@6332: { maedhros@6332: const Town *town = object->u.house.town; maedhros@6332: TileIndex tile = object->u.house.tile; maedhros@6332: HouseID house_id = object->u.house.house_id; maedhros@6332: maedhros@6332: if (object->scope == VSG_SCOPE_PARENT) { maedhros@6332: return TownGetVariable(variable, parameter, available, town); maedhros@6332: } maedhros@6332: maedhros@6332: switch (variable) { maedhros@6332: /* Construction stage. */ skidd13@8791: case 0x40: return (IsTileType(tile, MP_HOUSE) ? GetHouseBuildingStage(tile) : 0) | TileHash2Bit(TileX(tile), TileY(tile)) << 2; maedhros@6332: maedhros@6332: /* Building age. */ frosch@10360: case 0x41: return GetHouseAge(tile); maedhros@6332: maedhros@6332: /* Town zone */ maedhros@6332: case 0x42: return GetTownRadiusGroup(town, tile); maedhros@6332: maedhros@6332: /* Terrain type */ maedhros@6332: case 0x43: return GetTerrainType(tile); maedhros@6332: maedhros@6332: /* Number of this type of building on the map. */ maedhros@6332: case 0x44: return GetNumHouses(house_id, town); maedhros@6332: maedhros@6332: /* Whether the town is being created or just expanded. */ maedhros@6332: case 0x45: return _generating_world ? 1 : 0; maedhros@6332: maedhros@6332: /* Current animation frame. */ maedhros@6332: case 0x46: return IsTileType(tile, MP_HOUSE) ? GetHouseAnimationFrame(tile) : 0; maedhros@6332: belugas@10110: /* Position of the house */ belugas@10111: case 0x47: return TileY(tile) << 16 | TileX(tile); maedhros@6332: maedhros@6332: /* Building counts for old houses with id = parameter. */ maedhros@6332: case 0x60: return GetNumHouses(parameter, town); maedhros@6332: maedhros@6332: /* Building counts for new houses with id = parameter. */ maedhros@6332: case 0x61: { maedhros@6332: const HouseSpec *hs = GetHouseSpecs(house_id); maedhros@6332: if (hs->grffile == NULL) return 0; maedhros@6332: belugas@6629: HouseID new_house = _house_mngr.GetID(parameter, hs->grffile->grfid); maedhros@6332: return new_house == INVALID_HOUSE_ID ? 0 : GetNumHouses(new_house, town); maedhros@6332: } maedhros@6332: maedhros@6332: /* Land info for nearby tiles. */ belugas@6827: case 0x62: return GetNearbyTileInformation(parameter, tile); maedhros@6332: belugas@9523: /* Current animation frame of nearby house tiles */ belugas@9523: case 0x63: { belugas@9523: TileIndex testtile = GetNearbyTile(parameter, tile); belugas@9523: return IsTileType(testtile, MP_HOUSE) ? GetHouseAnimationFrame(testtile) : 0; belugas@9523: } belugas@9523: belugas@9574: /* Cargo acceptance history of nearby stations */ belugas@9574: /*case 0x64: not implemented yet */ belugas@9574: belugas@9574: /* Distance test for some house types */ belugas@9574: case 0x65: return GetDistanceFromNearbyHouse(parameter, tile, object->u.house.house_id); maedhros@6332: } maedhros@6332: maedhros@6332: DEBUG(grf, 1, "Unhandled house property 0x%X", variable); maedhros@6332: maedhros@6332: *available = false; maedhros@6332: return UINT_MAX; maedhros@6332: } maedhros@6332: maedhros@6332: static const SpriteGroup *HouseResolveReal(const ResolverObject *object, const SpriteGroup *group) maedhros@6332: { maedhros@6332: /* Houses do not have 'real' groups */ maedhros@6332: return NULL; maedhros@6332: } maedhros@6332: maedhros@6332: /** maedhros@6332: * NewHouseResolver(): maedhros@6332: * maedhros@6332: * Returns a resolver object to be used with feature 07 spritegroups. maedhros@6332: */ maedhros@6332: static void NewHouseResolver(ResolverObject *res, HouseID house_id, TileIndex tile, Town *town) maedhros@6332: { maedhros@6332: res->GetRandomBits = HouseGetRandomBits; maedhros@6332: res->GetTriggers = HouseGetTriggers; maedhros@6332: res->SetTriggers = HouseSetTriggers; maedhros@6332: res->GetVariable = HouseGetVariable; maedhros@6332: res->ResolveReal = HouseResolveReal; maedhros@6332: maedhros@6332: res->u.house.tile = tile; maedhros@6332: res->u.house.town = town; maedhros@6332: res->u.house.house_id = house_id; maedhros@6332: rubidium@7327: res->callback = CBID_NO_CALLBACK; maedhros@6332: res->callback_param1 = 0; maedhros@6332: res->callback_param2 = 0; maedhros@6332: res->last_value = 0; maedhros@6332: res->trigger = 0; maedhros@6332: res->reseed = 0; glx@8756: res->count = 0; frosch@9750: frosch@9750: const HouseSpec *hs = GetHouseSpecs(house_id); frosch@9750: res->grffile = (hs != NULL ? hs->grffile : NULL); maedhros@6332: } maedhros@6332: rubidium@7327: uint16 GetHouseCallback(CallbackID callback, uint32 param1, uint32 param2, HouseID house_id, Town *town, TileIndex tile) maedhros@6332: { maedhros@6332: ResolverObject object; maedhros@6332: const SpriteGroup *group; maedhros@6332: maedhros@6332: NewHouseResolver(&object, house_id, tile, town); maedhros@6332: object.callback = callback; maedhros@6332: object.callback_param1 = param1; peter1138@6642: object.callback_param2 = param2; maedhros@6332: maedhros@6332: group = Resolve(GetHouseSpecs(house_id)->spritegroup, &object); maedhros@6332: if (group == NULL || group->type != SGT_CALLBACK) return CALLBACK_FAILED; maedhros@6332: maedhros@6332: return group->g.callback.result; maedhros@6332: } maedhros@6332: frosch@10257: static void DrawTileLayout(const TileInfo *ti, const SpriteGroup *group, byte stage, HouseID house_id) maedhros@6332: { maedhros@6332: const DrawTileSprites *dts = group->g.layout.dts; maedhros@6332: const DrawTileSeqStruct *dtss; maedhros@6332: frosch@10355: const HouseSpec *hs = GetHouseSpecs(house_id); frosch@10355: SpriteID palette = hs->random_colour[TileHash2Bit(ti->x, ti->y)] + PALETTE_RECOLOR_START; frosch@10355: if (HasBit(hs->callback_mask, CBM_HOUSE_COLOUR)) { frosch@10355: uint16 callback = GetHouseCallback(CBID_HOUSE_COLOUR, 0, 0, house_id, GetTownByTile(ti->tile), ti->tile); frosch@10355: if (callback != CALLBACK_FAILED) { frosch@10355: /* If bit 14 is set, we should use a 2cc colour map, else use the callback value. */ frosch@10355: palette = HasBit(callback, 14) ? GB(callback, 0, 8) + SPR_2CCMAP_BASE : callback; frosch@10355: } frosch@10355: } frosch@10355: frosch@8571: SpriteID image = dts->ground.sprite; frosch@8571: SpriteID pal = dts->ground.pal; maedhros@6332: maedhros@7619: if (IS_CUSTOM_SPRITE(image)) image += stage; maedhros@7619: frosch@10355: if (GB(image, 0, SPRITE_WIDTH) != 0) { frosch@10355: DrawGroundSprite(image, GroundSpritePaletteTransform(image, pal, palette)); frosch@10355: } maedhros@6332: maedhros@6332: foreach_draw_tile_seq(dtss, dts->seq) { frosch@8570: if (GB(dtss->image.sprite, 0, SPRITE_WIDTH) == 0) continue; maedhros@6332: frosch@8570: image = dtss->image.sprite; frosch@8570: pal = dtss->image.pal; maedhros@6332: smatz@9549: /* Stop drawing sprite sequence once we meet a sprite that doesn't have to be opaque */ smatz@9549: if (IsInvisibilitySet(TO_HOUSES) && !HasBit(image, SPRITE_MODIFIER_OPAQUE)) return; smatz@9549: maedhros@7619: if (IS_CUSTOM_SPRITE(image)) image += stage; maedhros@7619: frosch@10355: pal = SpriteLayoutPaletteTransform(image, pal, palette); maedhros@6332: maedhros@6332: if ((byte)dtss->delta_z != 0x80) { maedhros@6332: AddSortableSpriteToDraw( maedhros@6332: image, pal, maedhros@6332: ti->x + dtss->delta_x, ti->y + dtss->delta_y, maedhros@6332: dtss->size_x, dtss->size_y, rubidium@7333: dtss->size_z, ti->z + dtss->delta_z, frosch@10257: !HasBit(image, SPRITE_MODIFIER_OPAQUE) && IsTransparencySet(TO_HOUSES) maedhros@6332: ); maedhros@6332: } else { frosch@10257: /* For industries and houses delta_x and delta_y are unsigned */ frosch@10257: AddChildSpriteScreen(image, pal, (byte)dtss->delta_x, (byte)dtss->delta_y, !HasBit(image, SPRITE_MODIFIER_OPAQUE) && IsTransparencySet(TO_HOUSES)); maedhros@6332: } maedhros@6332: } maedhros@6332: } maedhros@6332: maedhros@6332: void DrawNewHouseTile(TileInfo *ti, HouseID house_id) maedhros@6332: { maedhros@6332: const HouseSpec *hs = GetHouseSpecs(house_id); maedhros@6332: const SpriteGroup *group; maedhros@6332: ResolverObject object; maedhros@6332: rubidium@7335: if (ti->tileh != SLOPE_FLAT) DrawFoundation(ti, FOUNDATION_LEVELED); maedhros@6332: maedhros@6332: NewHouseResolver(&object, house_id, ti->tile, GetTownByTile(ti->tile)); maedhros@6332: maedhros@6332: group = Resolve(hs->spritegroup, &object); maedhros@6332: if (group == NULL || group->type != SGT_TILELAYOUT) { maedhros@6332: /* XXX: This is for debugging purposes really, and shouldn't stay. */ maedhros@6332: DrawGroundSprite(SPR_SHADOW_CELL, PAL_NONE); maedhros@6332: } else { maedhros@6332: /* Limit the building stage to the number of stages supplied. */ maedhros@6332: byte stage = GetHouseBuildingStage(ti->tile); skidd13@7922: stage = Clamp(stage - 4 + group->g.layout.num_sprites, 0, group->g.layout.num_sprites - 1); maedhros@6332: DrawTileLayout(ti, group, stage, house_id); maedhros@6332: } maedhros@6332: } maedhros@6332: maedhros@6332: void AnimateNewHouseTile(TileIndex tile) maedhros@6332: { maedhros@6332: const HouseSpec *hs = GetHouseSpecs(GetHouseType(tile)); maedhros@6332: byte animation_speed = hs->animation_speed; maedhros@6332: bool frame_set_by_callback = false; maedhros@6332: skidd13@7928: if (HasBit(hs->callback_mask, CBM_HOUSE_ANIMATION_SPEED)) { peter1138@6642: uint16 callback_res = GetHouseCallback(CBID_HOUSE_ANIMATION_SPEED, 0, 0, GetHouseType(tile), GetTownByTile(tile), tile); skidd13@7922: if (callback_res != CALLBACK_FAILED) animation_speed = Clamp(callback_res & 0xFF, 2, 16); maedhros@6332: } maedhros@6332: maedhros@6332: /* An animation speed of 2 means the animation frame changes 4 ticks, and maedhros@6332: * increasing this value by one doubles the wait. 2 is the minimum value maedhros@6332: * allowed for animation_speed, which corresponds to 120ms, and 16 is the maedhros@6332: * maximum, corresponding to around 33 minutes. */ maedhros@6332: if (_tick_counter % (1 << animation_speed) != 0) return; maedhros@6332: maedhros@6332: byte frame = GetHouseAnimationFrame(tile); maedhros@6332: byte num_frames = GB(hs->animation_frames, 0, 7); maedhros@6332: skidd13@7928: if (HasBit(hs->callback_mask, CBM_HOUSE_ANIMATION_NEXT_FRAME)) { maedhros@6332: uint32 param = (hs->extra_flags & CALLBACK_1A_RANDOM_BITS) ? Random() : 0; peter1138@6642: uint16 callback_res = GetHouseCallback(CBID_HOUSE_ANIMATION_NEXT_FRAME, param, 0, GetHouseType(tile), GetTownByTile(tile), tile); maedhros@6332: maedhros@6332: if (callback_res != CALLBACK_FAILED) { maedhros@6332: frame_set_by_callback = true; maedhros@6332: maedhros@6332: switch (callback_res & 0xFF) { maedhros@6332: case 0xFF: maedhros@6332: DeleteAnimatedTile(tile); maedhros@6332: break; maedhros@6332: case 0xFE: maedhros@6332: /* Carry on as normal. */ maedhros@6332: frame_set_by_callback = false; maedhros@6332: break; maedhros@6332: default: maedhros@6332: frame = callback_res & 0xFF; maedhros@6332: break; maedhros@6332: } maedhros@6332: maedhros@6332: /* If the lower 7 bits of the upper byte of the callback maedhros@6332: * result are not empty, it is a sound effect. */ rubidium@9018: if (GB(callback_res, 8, 7) != 0) PlayTileSound(hs->grffile, GB(callback_res, 8, 7), tile); maedhros@6332: } maedhros@6332: } maedhros@6332: maedhros@6332: if (!frame_set_by_callback) { maedhros@6332: if (frame < num_frames) { maedhros@6332: frame++; skidd13@7928: } else if (frame == num_frames && HasBit(hs->animation_frames, 7)) { maedhros@6332: /* This animation loops, so start again from the beginning */ maedhros@6332: frame = 0; maedhros@6332: } else { maedhros@6332: /* This animation doesn't loop, so stay here */ maedhros@6332: DeleteAnimatedTile(tile); maedhros@6332: } maedhros@6332: } maedhros@6332: maedhros@6332: SetHouseAnimationFrame(tile, frame); maedhros@6332: MarkTileDirtyByTile(tile); maedhros@6332: } maedhros@6332: rubidium@9018: void ChangeHouseAnimationFrame(const GRFFile *file, TileIndex tile, uint16 callback_result) maedhros@6332: { maedhros@6332: switch (callback_result & 0xFF) { maedhros@6332: case 0xFD: /* Do nothing. */ break; maedhros@6332: case 0xFE: AddAnimatedTile(tile); break; maedhros@6332: case 0xFF: DeleteAnimatedTile(tile); break; maedhros@6332: default: maedhros@6332: SetHouseAnimationFrame(tile, callback_result & 0xFF); maedhros@6332: AddAnimatedTile(tile); maedhros@6332: break; maedhros@6332: } maedhros@6332: /* If the lower 7 bits of the upper byte of the callback maedhros@6332: * result are not empty, it is a sound effect. */ rubidium@9018: if (GB(callback_result, 8, 7) != 0) PlayTileSound(file, GB(callback_result, 8, 7), tile); maedhros@6332: } maedhros@6332: maedhros@6332: bool CanDeleteHouse(TileIndex tile) maedhros@6332: { maedhros@6332: const HouseSpec *hs = GetHouseSpecs(GetHouseType(tile)); maedhros@6332: rubidium@10207: /* Humans are always allowed to remove buildings, as is water and maedhros@6332: * anyone using the scenario editor. */ rubidium@10207: if ((IsValidCompanyID(_current_company) && IsHumanCompany(_current_company)) rubidium@10207: || _current_company == OWNER_WATER || _current_company == OWNER_NONE) return true; maedhros@6332: skidd13@7928: if (HasBit(hs->callback_mask, CBM_HOUSE_DENY_DESTRUCTION)) { peter1138@6642: uint16 callback_res = GetHouseCallback(CBID_HOUSE_DENY_DESTRUCTION, 0, 0, GetHouseType(tile), GetTownByTile(tile), tile); maedhros@6332: return (callback_res == CALLBACK_FAILED || callback_res == 0); maedhros@6332: } else { maedhros@6332: return !(hs->extra_flags & BUILDING_IS_PROTECTED); maedhros@6332: } maedhros@6332: } maedhros@6332: maedhros@6332: static void AnimationControl(TileIndex tile, uint16 random_bits) maedhros@6332: { maedhros@6332: const HouseSpec *hs = GetHouseSpecs(GetHouseType(tile)); maedhros@6332: skidd13@7928: if (HasBit(hs->callback_mask, CBM_HOUSE_ANIMATION_START_STOP)) { maedhros@6332: uint32 param = (hs->extra_flags & SYNCHRONISED_CALLBACK_1B) ? (GB(Random(), 0, 16) | random_bits << 16) : Random(); peter1138@6642: uint16 callback_res = GetHouseCallback(CBID_HOUSE_ANIMATION_START_STOP, param, 0, GetHouseType(tile), GetTownByTile(tile), tile); maedhros@6332: rubidium@9018: if (callback_res != CALLBACK_FAILED) ChangeHouseAnimationFrame(hs->grffile, tile, callback_res); maedhros@6332: } maedhros@6332: } maedhros@6332: maedhros@6332: bool NewHouseTileLoop(TileIndex tile) maedhros@6332: { maedhros@6332: const HouseSpec *hs = GetHouseSpecs(GetHouseType(tile)); maedhros@6332: maedhros@6332: if (GetHouseProcessingTime(tile) > 0) { maedhros@6332: DecHouseProcessingTime(tile); maedhros@6332: return true; maedhros@6332: } maedhros@6332: rubidium@7861: TriggerHouse(tile, HOUSE_TRIGGER_TILE_LOOP); rubidium@7861: TriggerHouse(tile, HOUSE_TRIGGER_TILE_LOOP_TOP); maedhros@6332: skidd13@7928: if (HasBit(hs->callback_mask, CBM_HOUSE_ANIMATION_START_STOP)) { maedhros@6332: /* If this house is marked as having a synchronised callback, all the maedhros@6332: * tiles will have the callback called at once, rather than when the maedhros@6332: * tile loop reaches them. This should only be enabled for the northern maedhros@6332: * tile, or strange things will happen (here, and in TTDPatch). */ maedhros@6332: if (hs->extra_flags & SYNCHRONISED_CALLBACK_1B) { maedhros@6332: uint16 random = GB(Random(), 0, 16); maedhros@6332: maedhros@6332: if (hs->building_flags & BUILDING_HAS_1_TILE) AnimationControl(tile, random); maedhros@6332: if (hs->building_flags & BUILDING_2_TILES_Y) AnimationControl(TILE_ADDXY(tile, 0, 1), random); maedhros@6332: if (hs->building_flags & BUILDING_2_TILES_X) AnimationControl(TILE_ADDXY(tile, 1, 0), random); maedhros@6332: if (hs->building_flags & BUILDING_HAS_4_TILES) AnimationControl(TILE_ADDXY(tile, 1, 1), random); maedhros@6332: } else { maedhros@6332: AnimationControl(tile, 0); maedhros@6332: } maedhros@6332: } maedhros@6332: maedhros@6332: /* Check callback 21, which determines if a house should be destroyed. */ skidd13@7928: if (HasBit(hs->callback_mask, CBM_HOUSE_DESTRUCTION)) { peter1138@6642: uint16 callback_res = GetHouseCallback(CBID_HOUSE_DESTRUCTION, 0, 0, GetHouseType(tile), GetTownByTile(tile), tile); rubidium@9020: if (callback_res != CALLBACK_FAILED && GB(callback_res, 0, 8) > 0) { maedhros@6332: ClearTownHouse(GetTownByTile(tile), tile); maedhros@6332: return false; maedhros@6332: } maedhros@6332: } maedhros@6332: maedhros@6332: SetHouseProcessingTime(tile, hs->processing_time); maedhros@6332: return true; maedhros@6332: } rubidium@7861: rubidium@7861: static void DoTriggerHouse(TileIndex tile, HouseTrigger trigger, byte base_random, bool first) rubidium@7861: { rubidium@7861: ResolverObject object; rubidium@7861: rubidium@7861: /* We can't trigger a non-existent building... */ rubidium@7861: assert(IsTileType(tile, MP_HOUSE)); rubidium@7861: rubidium@7861: HouseID hid = GetHouseType(tile); rubidium@7861: HouseSpec *hs = GetHouseSpecs(hid); rubidium@7861: rubidium@9028: if (hs->spritegroup == NULL) return; rubidium@9028: rubidium@7861: NewHouseResolver(&object, hid, tile, GetTownByTile(tile)); rubidium@7861: rubidium@7861: object.callback = CBID_RANDOM_TRIGGER; rubidium@7861: object.trigger = trigger; rubidium@7861: rubidium@7861: const SpriteGroup *group = Resolve(hs->spritegroup, &object); rubidium@7861: if (group == NULL) return; rubidium@7861: rubidium@7861: byte new_random_bits = Random(); rubidium@7861: byte random_bits = GetHouseRandomBits(tile); rubidium@7861: random_bits &= ~object.reseed; rubidium@7861: random_bits |= (first ? new_random_bits : base_random) & object.reseed; rubidium@7861: SetHouseRandomBits(tile, random_bits); rubidium@7861: rubidium@7861: switch (trigger) { rubidium@7861: case HOUSE_TRIGGER_TILE_LOOP: rubidium@7861: /* Random value already set. */ rubidium@7861: break; rubidium@7861: rubidium@7861: case HOUSE_TRIGGER_TILE_LOOP_TOP: rubidium@7861: if (!first) break; rubidium@7861: /* Random value of first tile already set. */ belugas@7866: if (hs->building_flags & BUILDING_2_TILES_Y) DoTriggerHouse(TILE_ADDXY(tile, 0, 1), trigger, random_bits, false); belugas@7866: if (hs->building_flags & BUILDING_2_TILES_X) DoTriggerHouse(TILE_ADDXY(tile, 1, 0), trigger, random_bits, false); belugas@7866: if (hs->building_flags & BUILDING_HAS_4_TILES) DoTriggerHouse(TILE_ADDXY(tile, 1, 1), trigger, random_bits, false); rubidium@7861: break; rubidium@7861: } rubidium@7861: } rubidium@7861: rubidium@7861: void TriggerHouse(TileIndex t, HouseTrigger trigger) rubidium@7861: { glx@7868: DoTriggerHouse(t, trigger, 0, true); rubidium@7861: }