rubidium@9628: /* $Id$ */ rubidium@9628: rubidium@10455: /** @file newgrf_industrytiles.cpp NewGRF handling of industry tiles. */ rubidium@9628: rubidium@9628: #include "stdafx.h" rubidium@9628: #include "openttd.h" rubidium@9628: #include "variables.h" rubidium@9628: #include "debug.h" rubidium@9723: #include "viewport_func.h" rubidium@9628: #include "landscape.h" rubidium@9628: #include "newgrf.h" rubidium@9628: #include "industry.h" rubidium@9628: #include "newgrf_commons.h" rubidium@9628: #include "newgrf_spritegroup.h" rubidium@9628: #include "newgrf_callbacks.h" rubidium@9628: #include "newgrf_industries.h" truelight@9641: #include "newgrf_industrytiles.h" glx@10294: #include "newgrf_sound.h" truelight@9641: #include "newgrf_text.h" rubidium@9628: #include "industry_map.h" rubidium@9628: #include "clear_map.h" rubidium@9628: #include "sprite.h" rubidium@9722: #include "transparency.h" rubidium@9723: #include "functions.h" rubidium@9723: #include "town.h" rubidium@9724: #include "command_func.h" glx@10294: #include "animated_tile_func.h" rubidium@9724: rubidium@9724: #include "table/sprites.h" rubidium@9724: #include "table/strings.h" rubidium@9723: rubidium@9723: static uint32 GetGRFParameter(IndustryGfx indtile_id, byte parameter) rubidium@9723: { rubidium@9723: const IndustryTileSpec *indtspec = GetIndustryTileSpec(indtile_id); rubidium@9723: const GRFFile *file = indtspec->grf_prop.grffile; rubidium@9723: rubidium@9723: if (parameter >= file->param_end) return 0; rubidium@9723: return file->param[parameter]; rubidium@9723: } rubidium@9628: rubidium@9628: /** rubidium@9628: * Based on newhouses equivalent, but adapted for newindustries rubidium@9628: * @param parameter from callback. It's in fact a pair of coordinates rubidium@9628: * @param tile TileIndex from which the callback was initiated rubidium@9628: * @param index of the industry been queried for rubidium@9628: * @return a construction of bits obeying the newgrf format rubidium@9628: */ truelight@9641: uint32 GetNearbyIndustryTileInformation(byte parameter, TileIndex tile, IndustryID index) rubidium@9628: { rubidium@9701: if (parameter != 0) tile = GetNearbyTile(parameter, tile); // only perform if it is required rubidium@9724: bool is_same_industry = (IsTileType(tile, MP_INDUSTRY) && GetIndustryIndex(tile) == index); rubidium@9628: rubidium@9724: return GetNearbyTileInformation(tile) | (is_same_industry ? 1 : 0) << 8; rubidium@9628: } rubidium@9628: rubidium@9628: /** This is the position of the tile relative to the northernmost tile of the industry. rubidium@9628: * Format: 00yxYYXX rubidium@9628: * Variable Content rubidium@9628: * x the x offset from the northernmost tile rubidium@9628: * XX same, but stored in a byte instead of a nibble rubidium@9628: * y the y offset from the northernmost tile rubidium@9628: * YY same, but stored in a byte instead of a nibble rubidium@9628: * @param tile TileIndex of the tile to evaluate rubidium@9628: * @param ind_tile northernmost tile of the industry rubidium@9628: */ rubidium@9628: static uint32 GetRelativePosition(TileIndex tile, TileIndex ind_tile) rubidium@9628: { rubidium@9628: byte x = TileX(tile) - TileX(ind_tile); rubidium@9628: byte y = TileY(tile) - TileY(ind_tile); rubidium@9628: rubidium@9628: return ((y & 0xF) << 20) | ((x & 0xF) << 16) | (y << 8) | x; rubidium@9628: } rubidium@9628: rubidium@9628: static uint32 IndustryTileGetVariable(const ResolverObject *object, byte variable, byte parameter, bool *available) rubidium@9628: { rubidium@9628: const Industry *inds = object->u.industry.ind; rubidium@9628: TileIndex tile = object->u.industry.tile; rubidium@9628: rubidium@9628: if (object->scope == VSG_SCOPE_PARENT) { rubidium@9628: return IndustryGetVariable(object, variable, parameter, available); rubidium@9628: } rubidium@9628: rubidium@9628: switch (variable) { rubidium@9628: /* Construction state of the tile: a value between 0 and 3 */ rubidium@9628: case 0x40 : return (IsTileType(tile, MP_INDUSTRY)) ? GetIndustryConstructionStage(tile) : 0; rubidium@9628: rubidium@9628: case 0x41 : return GetTerrainType(tile); rubidium@9628: rubidium@9628: /* Current town zone of the tile in the nearest town */ rubidium@9628: case 0x42 : return GetTownRadiusGroup(ClosestTownFromTile(tile, (uint)-1), tile); rubidium@9628: rubidium@9628: /* Relative position */ rubidium@9628: case 0x43 : return GetRelativePosition(tile, inds->xy); rubidium@9628: rubidium@9628: /* Animation frame. Like house variable 46 but can contain anything 0..FF. */ rubidium@9628: case 0x44 : return (IsTileType(tile, MP_INDUSTRY)) ? GetIndustryAnimationState(tile) : 0; rubidium@9628: rubidium@9628: /* Land info of nearby tiles */ truelight@9641: case 0x60 : return GetNearbyIndustryTileInformation(parameter, tile, inds == NULL ? (IndustryID)INVALID_INDUSTRY : inds->index); rubidium@9628: truelight@9641: /* Animation stage of nearby tiles */ truelight@9641: case 0x61 : { rubidium@9628: tile = GetNearbyTile(parameter, tile); rubidium@9628: if (IsTileType(tile, MP_INDUSTRY) && GetIndustryByTile(tile) == inds) { rubidium@9628: return GetIndustryAnimationState(tile); rubidium@9628: } rubidium@9628: return 0xFFFFFFFF; rubidium@9628: } rubidium@9628: rubidium@9628: /* Get industry tile ID at offset */ glx@9704: case 0x62 : return GetIndustryIDAtOffset(GetNearbyTile(parameter, tile), inds); rubidium@9723: rubidium@9723: /* Read GRF parameter */ rubidium@9723: case 0x7F: return GetGRFParameter(GetIndustryGfx(tile), parameter); rubidium@9628: } rubidium@9628: truelight@9641: DEBUG(grf, 1, "Unhandled industry tile property 0x%X", variable); truelight@9641: truelight@9641: *available = false; truelight@9641: return (uint32)-1; rubidium@9628: } rubidium@9628: rubidium@9628: static const SpriteGroup *IndustryTileResolveReal(const ResolverObject *object, const SpriteGroup *group) rubidium@9628: { rubidium@9628: /* IndustryTile do not have 'real' groups. Or do they?? */ rubidium@9628: return NULL; rubidium@9628: } rubidium@9628: glx@9704: static uint32 IndustryTileGetRandomBits(const ResolverObject *object) rubidium@9628: { rubidium@9628: const TileIndex tile = object->u.industry.tile; glx@9704: if (tile == INVALID_TILE || !IsTileType(tile, MP_INDUSTRY)) return 0; rubidium@9722: return (object->scope == VSG_SCOPE_SELF) ? GetIndustryRandomBits(tile) : GetIndustryByTile(tile)->random; rubidium@9628: } rubidium@9628: glx@9704: static uint32 IndustryTileGetTriggers(const ResolverObject *object) rubidium@9628: { rubidium@9628: const TileIndex tile = object->u.industry.tile; glx@9704: if (tile == INVALID_TILE || !IsTileType(tile, MP_INDUSTRY)) return 0; rubidium@9722: return (object->scope == VSG_SCOPE_SELF) ? GetIndustryTriggers(tile) : GetIndustryByTile(tile)->random_triggers; rubidium@9628: } rubidium@9628: glx@9704: static void IndustryTileSetTriggers(const ResolverObject *object, int triggers) rubidium@9628: { rubidium@9628: const TileIndex tile = object->u.industry.tile; glx@9704: if (tile == INVALID_TILE || !IsTileType(tile, MP_INDUSTRY)) return; glx@9704: rubidium@9722: if (object->scope == VSG_SCOPE_SELF) { glx@9704: SetIndustryTriggers(tile, triggers); glx@9704: } else { rubidium@9722: GetIndustryByTile(tile)->random_triggers = triggers; glx@9704: } rubidium@9628: } rubidium@9628: rubidium@9628: static void NewIndustryTileResolver(ResolverObject *res, IndustryGfx gfx, TileIndex tile, Industry *indus) rubidium@9628: { rubidium@9628: res->GetRandomBits = IndustryTileGetRandomBits; rubidium@9628: res->GetTriggers = IndustryTileGetTriggers; rubidium@9628: res->SetTriggers = IndustryTileSetTriggers; rubidium@9628: res->GetVariable = IndustryTileGetVariable; rubidium@9628: res->ResolveReal = IndustryTileResolveReal; rubidium@9628: rubidium@9703: res->psa = &indus->psa; rubidium@9628: res->u.industry.tile = tile; rubidium@9628: res->u.industry.ind = indus; rubidium@9628: res->u.industry.gfx = gfx; rubidium@9724: res->u.industry.type = indus->type; rubidium@9628: rubidium@9694: res->callback = CBID_NO_CALLBACK; rubidium@9628: res->callback_param1 = 0; rubidium@9628: res->callback_param2 = 0; rubidium@9628: res->last_value = 0; rubidium@9628: res->trigger = 0; rubidium@9628: res->reseed = 0; rubidium@9826: res->count = 0; rubidium@9628: } rubidium@9628: glx@9629: void IndustryDrawTileLayout(const TileInfo *ti, const SpriteGroup *group, byte rnd_color, byte stage, IndustryGfx gfx) glx@9629: { glx@9629: const DrawTileSprites *dts = group->g.layout.dts; glx@9629: const DrawTileSeqStruct *dtss; glx@9629: glx@9732: SpriteID image = dts->ground.sprite; glx@9732: SpriteID pal = dts->ground.pal; glx@9629: glx@9704: if (IS_CUSTOM_SPRITE(image)) image += stage; glx@9704: glx@9629: if (GB(image, 0, SPRITE_WIDTH) != 0) DrawGroundSprite(image, pal); glx@9629: glx@9629: foreach_draw_tile_seq(dtss, dts->seq) { glx@9732: if (GB(dtss->image.sprite, 0, SPRITE_WIDTH) == 0) continue; glx@9629: glx@9732: image = dtss->image.sprite; glx@9732: pal = dtss->image.pal; glx@9629: rubidium@11044: /* Stop drawing sprite sequence once we meet a sprite that doesn't have to be opaque */ rubidium@11044: if (IsInvisibilitySet(TO_INDUSTRIES) && !HasBit(image, SPRITE_MODIFIER_OPAQUE)) return; rubidium@11044: glx@9704: if (IS_CUSTOM_SPRITE(image)) image += stage; glx@9704: rubidium@9722: if (HasBit(image, PALETTE_MODIFIER_COLOR)) { rubidium@9722: if (pal == 0) { rubidium@9722: pal = GENERAL_SPRITE_COLOR(rnd_color); rubidium@9722: } glx@9629: } else { glx@9629: pal = PAL_NONE; glx@9629: } glx@9629: glx@9629: if ((byte)dtss->delta_z != 0x80) { glx@9629: AddSortableSpriteToDraw( glx@9629: image, pal, glx@9629: ti->x + dtss->delta_x, ti->y + dtss->delta_y, glx@9629: dtss->size_x, dtss->size_y, rubidium@9694: dtss->size_z, ti->z + dtss->delta_z, rubidium@9722: !HasBit(image, SPRITE_MODIFIER_OPAQUE) && IsTransparencySet(TO_INDUSTRIES) glx@9629: ); glx@9629: } else { rubidium@9722: AddChildSpriteScreen(image, pal, (byte)dtss->delta_x, (byte)dtss->delta_y, IsTransparencySet(TO_INDUSTRIES)); glx@9629: } glx@9629: } glx@9629: } glx@9629: rubidium@9694: uint16 GetIndustryTileCallback(CallbackID callback, uint32 param1, uint32 param2, IndustryGfx gfx_id, Industry *industry, TileIndex tile) rubidium@9628: { rubidium@9628: ResolverObject object; rubidium@9628: const SpriteGroup *group; rubidium@9628: rubidium@9628: NewIndustryTileResolver(&object, gfx_id, tile, industry); rubidium@9628: object.callback = callback; rubidium@9628: object.callback_param1 = param1; rubidium@9628: object.callback_param2 = param2; rubidium@9628: rubidium@9628: group = Resolve(GetIndustryTileSpec(gfx_id)->grf_prop.spritegroup, &object); rubidium@9628: if (group == NULL || group->type != SGT_CALLBACK) return CALLBACK_FAILED; rubidium@9628: rubidium@9628: return group->g.callback.result; rubidium@9628: } glx@9629: glx@9629: bool DrawNewIndustryTile(TileInfo *ti, Industry *i, IndustryGfx gfx, const IndustryTileSpec *inds) glx@9629: { glx@9629: const SpriteGroup *group; glx@9629: ResolverObject object; glx@9629: glx@9629: if (ti->tileh != SLOPE_FLAT) { glx@9629: bool draw_old_one = true; rubidium@9722: if (HasBit(inds->callback_flags, CBM_INDT_DRAW_FOUNDATIONS)) { glx@9629: /* Called to determine the type (if any) of foundation to draw for industry tile */ glx@9629: uint32 callback_res = GetIndustryTileCallback(CBID_INDUSTRY_DRAW_FOUNDATIONS, 0, 0, gfx, i, ti->tile); truelight@9641: draw_old_one = callback_res != 0; glx@9629: } glx@9629: rubidium@9694: if (draw_old_one) DrawFoundation(ti, FOUNDATION_LEVELED); glx@9629: } glx@9629: glx@9629: NewIndustryTileResolver(&object, gfx, ti->tile, i); glx@9629: glx@9629: group = Resolve(inds->grf_prop.spritegroup, &object); glx@9629: if (group == NULL || group->type != SGT_TILELAYOUT) { glx@9629: return false; glx@9629: } else { glx@9629: /* Limit the building stage to the number of stages supplied. */ glx@9629: byte stage = GetIndustryConstructionStage(ti->tile); rubidium@9722: stage = Clamp(stage - 4 + group->g.layout.num_sprites, 0, group->g.layout.num_sprites - 1); glx@9629: IndustryDrawTileLayout(ti, group, i->random_color, stage, gfx); glx@9629: return true; glx@9629: } glx@9629: } truelight@9641: glx@9704: extern bool IsSlopeRefused(Slope current, Slope refused); glx@9704: glx@9704: bool PerformIndustryTileSlopeCheck(TileIndex ind_base_tile, TileIndex ind_tile, const IndustryTileSpec *its, IndustryType type, IndustryGfx gfx, uint itspec_index) truelight@9641: { truelight@9641: Industry ind; glx@9704: ind.index = INVALID_INDUSTRY; glx@9704: ind.xy = ind_base_tile; rubidium@9701: ind.width = 0; truelight@9641: ind.type = type; truelight@9641: glx@9704: uint16 callback_res = GetIndustryTileCallback(CBID_INDTILE_SHAPE_CHECK, 0, itspec_index, gfx, &ind, ind_tile); glx@9704: if (callback_res == CALLBACK_FAILED) { glx@9704: return !IsSlopeRefused(GetTileSlope(ind_tile, NULL), its->slopes_refused); glx@9704: } truelight@9641: if (its->grf_prop.grffile->grf_version < 7) { rubidium@10249: return callback_res != 0; truelight@9641: } truelight@9641: truelight@9718: /* Copy some parameters from the registers to the error message text ref. stack */ truelight@9718: SwitchToErrorRefStack(); truelight@9718: PrepareTextRefStackUsage(4); truelight@9718: SwitchToNormalRefStack(); truelight@9718: truelight@9641: switch (callback_res) { truelight@9641: case 0x400: return true; truelight@9641: case 0x401: _error_message = STR_0239_SITE_UNSUITABLE; return false; truelight@9641: case 0x402: _error_message = STR_0317_CAN_ONLY_BE_BUILT_IN_RAINFOREST; return false; truelight@9641: case 0x403: _error_message = STR_0318_CAN_ONLY_BE_BUILT_IN_DESERT; return false; truelight@9641: default: _error_message = GetGRFStringID(its->grf_prop.grffile->grfid, 0xD000 + callback_res); return false; truelight@9641: } truelight@9641: } truelight@9641: truelight@9641: void AnimateNewIndustryTile(TileIndex tile) truelight@9641: { truelight@9641: Industry *ind = GetIndustryByTile(tile); truelight@9641: IndustryGfx gfx = GetIndustryGfx(tile); truelight@9641: const IndustryTileSpec *itspec = GetIndustryTileSpec(gfx); truelight@9641: byte animation_speed = itspec->animation_speed; truelight@9641: rubidium@9722: if (HasBit(itspec->callback_flags, CBM_INDT_ANIM_SPEED)) { truelight@9641: uint16 callback_res = GetIndustryTileCallback(CBID_INDTILE_ANIMATION_SPEED, 0, 0, gfx, ind, tile); rubidium@9722: if (callback_res != CALLBACK_FAILED) animation_speed = Clamp(callback_res & 0xFF, 0, 16); truelight@9641: } truelight@9641: truelight@9641: /* An animation speed of 2 means the animation frame changes 4 ticks, and truelight@9641: * increasing this value by one doubles the wait. 0 is the minimum value truelight@9641: * allowed for animation_speed, which corresponds to 30ms, and 16 is the truelight@9641: * maximum, corresponding to around 33 minutes. */ truelight@9641: if ((_tick_counter % (1 << animation_speed)) != 0) return; truelight@9641: truelight@9641: bool frame_set_by_callback = false; truelight@9641: byte frame = GetIndustryAnimationState(tile); truelight@9718: uint16 num_frames = GB(itspec->animation_info, 0, 8); truelight@9641: rubidium@9722: if (HasBit(itspec->callback_flags, CBM_INDT_ANIM_NEXT_FRAME)) { rubidium@9722: uint16 callback_res = GetIndustryTileCallback(CBID_INDTILE_ANIM_NEXT_FRAME, HasBit(itspec->animation_special_flags, 0) ? Random() : 0, 0, gfx, ind, tile); truelight@9641: truelight@9641: if (callback_res != CALLBACK_FAILED) { truelight@9641: frame_set_by_callback = true; truelight@9641: truelight@9641: switch (callback_res & 0xFF) { truelight@9641: case 0xFF: truelight@9641: DeleteAnimatedTile(tile); truelight@9641: break; truelight@9641: case 0xFE: truelight@9641: /* Carry on as normal. */ truelight@9641: frame_set_by_callback = false; truelight@9641: break; truelight@9641: default: truelight@9641: frame = callback_res & 0xFF; truelight@9641: break; truelight@9641: } glx@10294: glx@10294: /* If the lower 7 bits of the upper byte of the callback glx@10294: * result are not empty, it is a sound effect. */ glx@10294: if (GB(callback_res, 8, 7) != 0) PlayTileSound(itspec->grf_prop.grffile, GB(callback_res, 8, 7), tile); truelight@9641: } truelight@9641: } truelight@9641: truelight@9641: if (!frame_set_by_callback) { truelight@9641: if (frame < num_frames) { truelight@9641: frame++; truelight@9641: } else if (frame == num_frames && GB(itspec->animation_info, 8, 8) == 1) { truelight@9641: /* This animation loops, so start again from the beginning */ truelight@9641: frame = 0; truelight@9641: } else { truelight@9641: /* This animation doesn't loop, so stay here */ truelight@9641: DeleteAnimatedTile(tile); truelight@9641: } truelight@9641: } truelight@9641: truelight@9641: SetIndustryAnimationState(tile, frame); truelight@9641: MarkTileDirtyByTile(tile); truelight@9641: } truelight@9641: glx@10294: static void ChangeIndustryTileAnimationFrame(const IndustryTileSpec *itspec, TileIndex tile, IndustryAnimationTrigger iat, uint32 random_bits, IndustryGfx gfx, Industry *ind) truelight@9641: { truelight@9641: uint16 callback_res = GetIndustryTileCallback(CBID_INDTILE_ANIM_START_STOP, random_bits, iat, gfx, ind, tile); truelight@9641: if (callback_res == CALLBACK_FAILED) return; truelight@9641: truelight@9641: switch (callback_res & 0xFF) { truelight@9641: case 0xFD: /* Do nothing. */ break; truelight@9641: case 0xFE: AddAnimatedTile(tile); break; truelight@9641: case 0xFF: DeleteAnimatedTile(tile); break; truelight@9641: default: truelight@9641: SetIndustryAnimationState(tile, callback_res & 0xFF); truelight@9641: AddAnimatedTile(tile); truelight@9641: break; truelight@9641: } glx@10294: glx@10294: /* If the lower 7 bits of the upper byte of the callback glx@10294: * result are not empty, it is a sound effect. */ glx@10294: if (GB(callback_res, 8, 7) != 0) PlayTileSound(itspec->grf_prop.grffile, GB(callback_res, 8, 7), tile); truelight@9641: } truelight@9641: truelight@9641: bool StartStopIndustryTileAnimation(TileIndex tile, IndustryAnimationTrigger iat, uint32 random) truelight@9641: { truelight@9641: IndustryGfx gfx = GetIndustryGfx(tile); truelight@9641: const IndustryTileSpec *itspec = GetIndustryTileSpec(gfx); truelight@9641: rubidium@9722: if (!HasBit(itspec->animation_triggers, iat)) return false; truelight@9641: truelight@9641: Industry *ind = GetIndustryByTile(tile); glx@10294: ChangeIndustryTileAnimationFrame(itspec, tile, iat, random, gfx, ind); truelight@9641: return true; truelight@9641: } truelight@9641: truelight@9641: bool StartStopIndustryTileAnimation(const Industry *ind, IndustryAnimationTrigger iat) truelight@9641: { truelight@9641: bool ret = true; truelight@9641: uint32 random = Random(); truelight@9641: BEGIN_TILE_LOOP(tile, ind->width, ind->height, ind->xy) truelight@9641: if (IsTileType(tile, MP_INDUSTRY) && GetIndustryIndex(tile) == ind->index) { glx@9704: if (StartStopIndustryTileAnimation(tile, iat, random)) { glx@9704: SB(random, 0, 16, Random()); glx@9704: } else { glx@9704: ret = false; glx@9704: } truelight@9641: } truelight@9641: END_TILE_LOOP(tile, ind->width, ind->height, ind->xy) truelight@9641: truelight@9641: return ret; truelight@9641: } rubidium@9722: rubidium@9722: static void DoTriggerIndustryTile(TileIndex tile, IndustryTileTrigger trigger, Industry *ind) rubidium@9722: { rubidium@9722: ResolverObject object; rubidium@9722: rubidium@9722: IndustryGfx gfx = GetIndustryGfx(tile); rubidium@9722: const IndustryTileSpec *itspec = GetIndustryTileSpec(gfx); rubidium@9722: rubidium@10355: if (itspec->grf_prop.spritegroup == NULL) return; rubidium@10355: rubidium@9722: NewIndustryTileResolver(&object, gfx, tile, ind); rubidium@9722: rubidium@9722: object.callback = CBID_RANDOM_TRIGGER; rubidium@9722: object.trigger = trigger; rubidium@9722: rubidium@9722: const SpriteGroup *group = Resolve(itspec->grf_prop.spritegroup, &object); rubidium@9722: if (group == NULL) return; rubidium@9722: rubidium@9722: byte new_random_bits = Random(); rubidium@9722: byte random_bits = GetIndustryRandomBits(tile); rubidium@9722: random_bits &= ~object.reseed; rubidium@9722: random_bits |= new_random_bits & object.reseed; rubidium@9722: SetIndustryRandomBits(tile, random_bits); rubidium@9722: } rubidium@9722: rubidium@9722: void TriggerIndustryTile(TileIndex tile, IndustryTileTrigger trigger) rubidium@9722: { rubidium@9722: DoTriggerIndustryTile(tile, trigger, GetIndustryByTile(tile)); rubidium@9722: } rubidium@9722: rubidium@9722: void TriggerIndustry(Industry *ind, IndustryTileTrigger trigger) rubidium@9722: { rubidium@9722: BEGIN_TILE_LOOP(tile, ind->width, ind->height, ind->xy) rubidium@9722: if (IsTileType(tile, MP_INDUSTRY) && GetIndustryIndex(tile) == ind->index) { rubidium@9722: DoTriggerIndustryTile(tile, trigger, ind); rubidium@9722: } rubidium@9722: END_TILE_LOOP(tile, ind->width, ind->height, ind->xy) rubidium@9722: }