Iscript::CmdResult Image::ConstIscriptCommand(Iscript::Context *ctx, Iscript::Script *script, const Iscript::Command &cmd) const { using namespace Iscript::Opcode; using Iscript::CmdResult; switch (cmd.opcode) { case Goto: script->pos = cmd.pos; break; case Call: script->return_pos = script->pos; script->pos = cmd.pos; break; case Return: script->pos = script->return_pos; break; case RandCondJmp: if (cmd.val > ctx->rng->Rand(256)) script->pos = cmd.pos; break; case Wait: script->wait = cmd.val - 1; return CmdResult::Stop; break; case WaitRand: script->wait = cmd.val1() + ctx->rng->Rand(cmd.val2() - cmd.val1() + 1); return CmdResult::Stop; break; case End: // This generally gets handled by sprite, but needs to be doen here as well // for the speed prediction. return CmdResult::Stop; break; case PwrupCondJmp: if (parent && parent->main_image != this) script->pos = cmd.pos; break; default: return CmdResult::NotHandled; break; } return CmdResult::Handled; }
virtual Iscript::CmdResult HandleCommand(Image *img, Iscript::Script *script, const Iscript::Command &cmd) override { auto result = img->HandleIscriptCommand(this, script, cmd); if (result == Iscript::CmdResult::NotHandled) { Warning("Could not handle iscript command %s for image %s from SetIscriptAnimation hook", cmd.DebugStr().c_str(), img->DebugStr().c_str()); } return result; }
virtual Iscript::CmdResult HandleCommand(Image *img, Iscript::Script *script, const Iscript::Command &cmd) override { // Firebat's attack sprite has this in normal bw, and it is // lone when the firebat is inside bunker. if (cmd.opcode == Iscript::Opcode::NoBrkCodeEnd || cmd.opcode == Iscript::Opcode::GotoRepeatAttk) return Iscript::CmdResult::Handled; Iscript::CmdResult result = sprite->HandleIscriptCommand(this, img, script, cmd); if (result == Iscript::CmdResult::NotHandled) { Warning("Unhandled iscript command %s in %s, image %s", cmd.DebugStr().c_str(), caller, img->DebugStr().c_str()); } return result; }
bool Image::IscriptCmd(const Iscript::Command &cmd, IscriptContext *ctx, Rng *rng) { using namespace IscriptOpcode; switch (cmd.opcode) { case PlayFram: if (cmd.val + direction < grp->frame_count) SetFrame(cmd.val); else Warning("Iscript for image %x sets image to invalid frame %x", image_id, cmd.val); break; case PlayFramTile: if (cmd.val + *bw::tileset < grp->frame_count) SetFrame(cmd.val + *bw::tileset); break; case SetHorPos: SetOffset(cmd.point.x, y_off); break; case SetVertPos: if (!ctx->unit || !ctx->unit->IsInvisible()) SetOffset(x_off, cmd.point.y); break; case SetPos: SetOffset(cmd.point.x, cmd.point.y); break; case Wait: iscript.wait = cmd.val - 1; break; case WaitRand: iscript.wait = cmd.val1() + rng->Rand(cmd.val2() - cmd.val1() + 1); break; case ImgOl: case ImgUl: Iscript_AddOverlay(ctx, cmd.val, x_off + cmd.point.x, y_off + cmd.point.y, cmd.opcode == ImgOl); break; case ImgOlOrig: case SwitchUl: { Image *other = Iscript_AddOverlay(ctx, cmd.val, 0, 0, cmd.opcode == ImgOlOrig); if (other && ~other->flags & 0x80) { other->flags |= 0x80; SetOffsetToParentsSpecialOverlay(other); } } break; case ImgOlUseLo: case ImgUlUseLo: { // Yeah, it's not actually point Point32 point = LoFile::GetOverlay(image_id, cmd.point.x).GetValues(this, cmd.point.y); Iscript_AddOverlay(ctx, cmd.val, point.x + x_off, point.y + y_off, cmd.opcode == ImgOlUseLo); } break; case ImgUlNextId: Iscript_AddOverlay(ctx, image_id + 1, cmd.point.x + x_off, cmd.point.y + y_off, false); break; case SprOl: { int sprite_id = cmd.val; if (ctx->bullet && ctx->bullet->parent && ctx->bullet->parent->IsGoliath()) { Unit *goliath = ctx->bullet->parent; if (GetUpgradeLevel(Upgrade::CharonBooster, goliath->player) || (units_dat_flags[goliath->unit_id] & UnitFlags::Hero && *bw::is_bw)) { sprite_id = Sprite::HaloRocketsTrail; } } Sprite::Spawn(this, sprite_id, cmd.point, parent->elevation + 1); } break; case HighSprOl: Sprite::Spawn(this, cmd.val, cmd.point, parent->elevation - 1); break; case LowSprUl: Sprite::Spawn(this, cmd.val, cmd.point, 1); break; case UflUnstable: { Warning("Flingy creation not implemented (image %x)", image_id); /*Flingy *flingy = CreateFlingy(cmd.val, parent->player, cmd.point); if (flingy) { flingy->GiveRandomMoveTarget(rng); flingy->sprite->UpdateVisibilityPoint(); }*/ } break; case SprUlUseLo: case SprUl: { int elevation = parent->elevation; if (cmd.opcode == SprUl) elevation -= 1; if (!ctx->unit || !ctx->unit->IsInvisible() || images_dat_draw_if_cloaked[cmd.val]) { Sprite *sprite = Sprite::Spawn(this, cmd.val, cmd.point, elevation); if (sprite) { if (flags & 0x2) sprite->SetDirection32(32 - direction); else sprite->SetDirection32(direction); } } } break; case SprOlUseLo: { // Again using the "point" for additional storage Point32 point = LoFile::GetOverlay(image_id, cmd.point.x).GetValues(this, 0); Sprite *sprite = Sprite::Spawn(this, cmd.val, point.ToPoint16(), parent->elevation + 1); if (sprite) { if (flags & 0x2) sprite->SetDirection32(32 - direction); else sprite->SetDirection32(direction); } } break; case SetFlipState: SetFlipping(cmd.val); break; case PlaySnd: PlaySoundAtPos(cmd.val, parent->position.AsDword(), 1, 0); break; case PlaySndRand: { int num = rng->Rand(cmd.data[0]); int sound = *(uint16_t *)(cmd.data + 1 + num * 2); PlaySoundAtPos(sound, parent->position.AsDword(), 1, 0); } break; case PlaySndBtwn: { int num = rng->Rand(cmd.val2() - cmd.val1() + 1); PlaySoundAtPos(cmd.val1() + num, parent->position.AsDword(), 1, 0); } break; case FollowMainGraphic: if (parent->main_image) { Image *main_img = parent->main_image; if (main_img->frame != frame || (main_img->flags & 0x2) != (flags & 0x2)) { int new_frame = main_img->frameset + main_img->direction; if (new_frame >= grp->frame_count) Warning("Iscript for image %x requested to play frame %x with followmaingraphic", image_id, new_frame); else { frameset = main_img->frameset; direction = main_img->direction; SetFlipping(main_img->flags & 0x2); UpdateFrameToDirection(); } } } break; case TurnCcWise: case TurnCWise: case Turn1CWise: { Flingy *entity = ctx->unit != nullptr ? (Flingy *)ctx->unit : (Flingy *)ctx->bullet; int direction = 1; if (cmd.opcode == TurnCcWise) direction = 0 - cmd.val; else if (cmd.opcode == TurnCWise) direction = cmd.val; SetDirection(entity, entity->facing_direction + direction * 8); } break; case SetFlDirect: { Flingy *entity = ctx->unit != nullptr ? (Flingy *)ctx->unit : (Flingy *)ctx->bullet; SetDirection(entity, cmd.val * 8); } break; case TurnRand: { Flingy *entity = ctx->unit != nullptr ? (Flingy *)ctx->unit : (Flingy *)ctx->bullet; int num = rng->Rand(4); if (num == 0) SetDirection(entity, entity->facing_direction - cmd.val * 8); else SetDirection(entity, entity->facing_direction + cmd.val * 8); } break; case SetSpawnFrame: SetMoveTargetToNearbyPoint(cmd.val, ctx->unit); break; case SigOrder: case OrderDone: { Unit *entity = ctx->unit != nullptr ? ctx->unit : (Unit *)ctx->bullet; if (cmd.opcode == SigOrder) entity->order_signal |= cmd.val; else entity->order_signal &= ~cmd.val; } break; case AttackWith: Iscript_AttackWith(ctx->unit, cmd.val); break; case Attack: if (!ctx->unit->target || ctx->unit->target->IsFlying()) Iscript_AttackWith(ctx->unit, 0); else Iscript_AttackWith(ctx->unit, 1); break; case CastSpell: if (orders_dat_targeting_weapon[ctx->unit->order] != Weapon::None && !ShouldStopOrderedSpell(ctx->unit)) FireWeapon(ctx->unit, orders_dat_targeting_weapon[ctx->unit->order]); break; case UseWeapon: Iscript_UseWeapon(cmd.val, ctx->unit); break; case GotoRepeatAttk: if (ctx->unit) ctx->unit->flingy_flags &= ~0x8; break; case EngFrame: frameset = cmd.val; direction = parent->main_image->direction; SetFlipping(parent->main_image->IsFlipped()); UpdateFrameToDirection(); break; case EngSet: frameset = parent->main_image->frameset + parent->main_image->grp->frame_count * cmd.val; direction = parent->main_image->direction; SetFlipping(parent->main_image->IsFlipped()); UpdateFrameToDirection(); break; case HideCursorMarker: *bw::draw_cursor_marker = 0; break; case NoBrkCodeStart: ctx->unit->flags |= UnitStatus::Nobrkcodestart; ctx->unit->sprite->flags |= 0x80; break; case NoBrkCodeEnd: if (ctx->unit) { ctx->unit->flags &= ~UnitStatus::Nobrkcodestart; ctx->unit->sprite->flags &= ~0x80; if (ctx->unit->order_queue_begin && ctx->unit->order_flags & 0x1) { ctx->unit->IscriptToIdle(); ctx->unit->DoNextQueuedOrder(); } } // This actually is feature ._. bunkers can create lone flamethrower sprites // whose default iscript has nobreakcodeend //else //Warning("Iscript for image %x used nobrkcodeend without having unit", image_id); break; case IgnoreRest: if (ctx->unit->target == nullptr) ctx->unit->IscriptToIdle(); else { iscript.wait = 10; iscript.pos -= cmd.Size(); // Loop on this cmd } break; case AttkShiftProj: // Sigh weapons_dat_x_offset[ctx->unit->GetGroundWeapon()] = cmd.val; Iscript_AttackWith(ctx->unit, 1); break; case TmpRmGraphicStart: Hide(); break; case TmpRmGraphicEnd: Show(); break; case SetFlSpeed: ctx->unit->flingy_top_speed = cmd.val; break; case CreateGasOverlays: { Image *img = Allocate(); if (parent->first_overlay == this) { Assert(list.prev == nullptr); parent->first_overlay = img; } img->list.prev = list.prev; img->list.next = this; if (list.prev) list.prev->list.next = img; list.prev = img; int smoke_img = VespeneSmokeOverlay1 + cmd.val; // Bw can be misused to have this check for loaded nuke and such // Even though resource_amount is word, it won't report incorrect values as unit array starts from 0x0059CCA8 // (The lower word is never 0 if the union contains unit) // But with dynamic allocation, that is not the case if (units_dat_flags[ctx->unit->unit_id] & UnitFlags::ResourceContainer) { if (ctx->unit->building.resource.resource_amount == 0) smoke_img = VespeneSmallSmoke1 + cmd.val; } else { if (ctx->unit->building.silo.nuke == nullptr) smoke_img = VespeneSmallSmoke1 + cmd.val; } Point pos = LoFile::GetOverlay(image_id, Overlay::Special).GetValues(this, cmd.val).ToPoint16(); InitializeImageFull(smoke_img, img, pos.x + x_off, pos.y + y_off, parent); } break; case WarpOverlay: flags |= 0x1; drawfunc_param = (void *)cmd.val; break; case GrdSprOl: { int x = parent->position.x + x_off + cmd.point.x; int y = parent->position.y + y_off + cmd.point.y; // Yes, it checks if unit id 0 can fit there if (DoesFitHere(Unit::Marine, x, y)) Sprite::Spawn(this, cmd.val, cmd.point, parent->elevation + 1); } break; default: return false; } return true; }
Iscript::CmdResult Image::HandleIscriptCommand(Iscript::Context *ctx, Iscript::Script *script, const Iscript::Command &cmd) { using namespace Iscript::Opcode; switch (cmd.opcode) { case PlayFram: if (cmd.val + direction < grp->frame_count) SetFrame(cmd.val); else Warning("Iscript for image %s sets image to invalid frame %x", DebugStr().c_str(), cmd.val); break; case PlayFramTile: if (cmd.val + *bw::tileset < grp->frame_count) SetFrame(cmd.val + *bw::tileset); break; case SetHorPos: SetOffset(cmd.point.x, y_off); break; case SetVertPos: SetOffset(x_off, cmd.point.y); break; case SetPos: SetOffset(cmd.point.x, cmd.point.y); break; case ImgOl: Iscript_AddOverlay(ctx, cmd.val, x_off + cmd.point.x, y_off + cmd.point.y, true); break; case ImgUl: Iscript_AddOverlay(ctx, cmd.val, x_off + cmd.point.x, y_off + cmd.point.y, false); break; case ImgOlOrig: case SwitchUl: { Image *other = Iscript_AddOverlay(ctx, cmd.val, 0, 0, cmd.opcode == ImgOlOrig); if (other != nullptr && ~other->flags & ImageFlags::UseParentLo) { other->flags |= ImageFlags::UseParentLo; SetOffsetToParentsSpecialOverlay(other); } } break; case ImgOlUseLo: case ImgUlUseLo: { // Yeah, it's not actually point Point32 point = LoFile::GetOverlay(image_id, cmd.point.x).GetValues(this, cmd.point.y); Iscript_AddOverlay(ctx, cmd.val, point.x + x_off, point.y + y_off, cmd.opcode == ImgOlUseLo); } break; case ImgUlNextId: Iscript_AddOverlay(ctx, image_id + 1, cmd.point.x + x_off, cmd.point.y + y_off, false); break; case SprOl: // Bullet's iscript handler has an override for goliath range upgrade Sprite::Spawn(this, cmd.val, cmd.point, parent->elevation + 1); break; case HighSprOl: Sprite::Spawn(this, cmd.val, cmd.point, parent->elevation - 1); break; case LowSprUl: Sprite::Spawn(this, cmd.val, cmd.point, 1); break; case UflUnstable: { Warning("Flingy creation not implemented (image %s)", DebugStr().c_str()); /*Flingy *flingy = CreateFlingy(cmd.val, parent->player, cmd.point); if (flingy) { flingy->GiveRandomMoveTarget(ctx->rng); flingy->sprite->UpdateVisibilityPoint(); }*/ } break; case SprUlUseLo: case SprUl: { int elevation = parent->elevation; if (cmd.opcode == SprUl) elevation -= 1; Sprite *sprite = Sprite::Spawn(this, cmd.val, cmd.point, elevation); if (sprite != nullptr) { if (IsFlipped()) sprite->SetDirection32(32 - direction); else sprite->SetDirection32(direction); } } break; case SprOlUseLo: { // Again using the "point" for additional storage Point32 point = LoFile::GetOverlay(image_id, cmd.point.x).GetValues(this, 0); Sprite *sprite = Sprite::Spawn(this, cmd.val, point.ToPoint16(), parent->elevation + 1); if (sprite) { if (IsFlipped()) sprite->SetDirection32(32 - direction); else sprite->SetDirection32(direction); } } break; case SetFlipState: SetFlipping(cmd.val); break; case PlaySnd: case PlaySndRand: case PlaySndBtwn: { int sound_id; if (cmd.opcode == PlaySnd) sound_id = cmd.val; else if (cmd.opcode == PlaySndRand) sound_id = *(uint16_t *)(cmd.data + 1 + ctx->rng->Rand(cmd.data[0]) * 2); else // PlaySndBtwn sound_id = cmd.val1() + ctx->rng->Rand(cmd.val2() - cmd.val1() + 1); PlaySoundAtPos(sound_id, parent->position.AsDword(), 1, 0); } break; case FollowMainGraphic: if (parent->main_image != nullptr) { Image *main_img = parent->main_image; if (main_img->frame != frame || main_img->IsFlipped() != IsFlipped()) { int new_frame = main_img->frameset + main_img->direction; if (new_frame >= grp->frame_count) { Warning("Iscript for image %s requested to play frame %x with followmaingraphic", DebugStr().c_str(), new_frame); } else { frameset = main_img->frameset; FollowMainImage(); } } } break; case EngFrame: case EngSet: if (cmd.opcode == EngFrame) frameset = cmd.val; else // EndSet frameset = parent->main_image->frameset + parent->main_image->grp->frame_count * cmd.val; FollowMainImage(); break; case HideCursorMarker: *bw::draw_cursor_marker = 0; break; case TmpRmGraphicStart: Hide(); break; case TmpRmGraphicEnd: Show(); break; case WarpOverlay: flags |= ImageFlags::Redraw; drawfunc_param = (void *)cmd.val; break; case GrdSprOl: { int x = parent->position.x + x_off + cmd.point.x; int y = parent->position.y + y_off + cmd.point.y; // Yes, it checks if unit id 0 can fit there if (DoesFitHere(Unit::Marine, x, y)) Sprite::Spawn(this, cmd.val, cmd.point, parent->elevation + 1); } break; default: return ConstIscriptCommand(ctx, script, cmd); break; } return Iscript::CmdResult::Handled; }