BubbleEm
Table of Contents
{ "old_version": "1.1.0" }
1. About
With this add-on don't die, they're put in safe bubbles instead.
Features
- cosmetic (doesn't affect gameplay much)
- universal, see below
- sound effects
Compatibility
- should be compatible with every UZDoom game and mod. However, if death animations include body parts that fly away, with this mod they will still fly away.
- Zrift - Chasm in Doom - LE: enemies are invisible inside bubbles (checked on v1.1).
- mk-champions (Champions): invisible enemies (checked on v2.666).
2. Changelog
v1.1.0
- always sync bubble and corpse position
- use separate random numbers
- fade out for destroyed actors
- tweak bubble sizes
v1.0.1
- prevent bubbling non-living things
- fix boss death events
- fix bubbles for some mods
v1.0
- add pop sound
v0.2
- fix VM abort on resurrection
- add roll to bubbled enemies
- add tag for bubble
- new bubble graphics by Accensus
v0.1: initial release
3. License
SPDX-FileCopyrightText: © 2020 Alexander Kromm (m8f) <mmaulwurff@gmail.com> SPDX-License-Identifier: GPL-3.0-only
4. Project setup
GameInfo
{
EventHandlers = "be_EventHandler"
}
be/swoosh "sounds/be_swoosh.ogg" be/pop "sounds/be_pop.ogg"
Sprite be_2A0, 48, 40 { Patch be_1A0, 0, 0 Offset 24, 20 XScale 4 YScale 4 }
version 4.14.3 #include "zscript/be_EventHandler.zs" #include "zscript/be_Bubble.zs" #include "zscript/be_Magic.zs"
5. Source
5.1. Event handler
class be_EventHandler : EventHandler { override void WorldThingDied(WorldEvent event) { if (isValid(event)) { inflate(event.thing); } } override void WorldThingRevived(WorldEvent event) { if (isValid(event)) { deflate(event.thing); } } override void WorldThingSpawned(WorldEvent event) { if (event.thing) { event.thing.bNoExtremeDeath = true; } } override void WorldThingDestroyed(WorldEvent event) { if (isValid(event)) { transferMagic(event.thing); } } private static void transferMagic(Actor destroyed) { let magic = findMagic(destroyed); if (magic) { magic.transfer(); } } private static bool isValid(WorldEvent event) { if (event == NULL) return false; let thing = event.thing; return !(thing == NULL || thing is "PlayerPawn") && thing.bIsMonster; } private static void inflate(Actor died) { died.giveInventory(MAGIC_CLASS, 1); let magic = findMagic(died); if (magic) { magic.init(died); } } private static void deflate(Actor revived) { let magic = findMagic(revived); if (magic) { magic.restore(); } } private static be_Magic findMagic(Actor a) { return be_Magic(a.findInventory(MAGIC_CLASS)); } const MAGIC_CLASS = "be_Magic"; }
5.2. Bubble
class be_Bubble : Actor { Default { +Nogravity; +NoInteraction; +ForceXYBillboard; FloatBobStrength 0.2; RenderStyle 'Stencil'; Alpha 0; XScale 6.66; YScale 6.66; Tag "Bubble"; } States { Spawn: BE_2 A 1; loop; } }
5.3. Magic
class be_Magic : Inventory { void init(Actor original) { initOriginal(original); initPretender(); initBubble(); mLifetime = 0; mDestroyTime = 0; } void restore() { mOriginal.A_SetRenderStyle(mOriginalAlpha, mOriginalRenderStyle); mOriginal.A_StartSound("be/pop"); mPretender.destroy(); mBubble.destroy(); GoAwayAndDie(); } void transfer() { mOriginal.RemoveInventory(self); callTryPickup(mPretender); mDestroyTime = mLifetime; } private void initPretender() { let pos = makePretenderPos(); let pretender = Actor.Spawn(mOriginal.getClass(), pos, false); mPretender = pretender; pretender.angle = mOriginal.angle; pretender.floatBobStrength = 0.2; pretender.bFloatBob = true; pretender.bShootable = false; pretender.bNoInteraction = true; pretender.bNogravity = true; pretender.bIsMonster = false; pretender.bForceXYBillboard = true; pretender.bRollSprite = true; pretender.bRollCenter = true; pretender.health = -1; pretender.A_SetTics(int.Max); mOrigScale = pretender.scale; mRoll = random[BubbleEm](-4, 4) / 10.0; level.total_monsters -= pretender.bCountKill; pretender.A_StartSound("be/swoosh"); } private void initOriginal(Actor original) { mOriginal = original; mOriginalRenderStyle = original.GetRenderStyle(); mOriginalAlpha = original.alpha; mOriginal.A_SetRenderStyle(0.0, STYLE_None); // no need to save, it won't be used anymore. mOriginal.deathSound = ""; } private void initBubble() { Vector3 bubblePos = makeBubblePos(); mBubble = Actor.Spawn("be_Bubble", bubblePos); double bubbleScale = max(mPretender.radius / 28, mPretender.height / 28); mOrigBubbleScale = mBubble.scale * bubbleScale; mBubble.floatBobPhase = mPretender.floatBobPhase; let shade = string.format("%x%x%x%x%x%x", random[BubbleEm](9, 0xc), random[BubbleEm](9, 0xc), random[BubbleEm](9, 0xc), random[BubbleEm](9, 0xc), random[BubbleEm](9, 0xc), random[BubbleEm](9, 0xc)); mBubble.setShade(shade); } override void Tick() { if (mBubble == NULL || mPretender == NULL) { return; } if (mLifetime < DURATION) { double ratio = double(mLifetime) / DURATION; double scaleRatio = (1 - ratio) * (1 - TARGET_SCALE) + TARGET_SCALE; mPretender.scale = scaleRatio * mOrigScale; mBubble.scale = scaleRatio * mOrigBubbleScale; mBubble.alpha = ratio * TARGET_ALPHA; mBubble.setOrigin(mPretender.pos + (0, 0, mPretender.height * scaleRatio / 2), true); } else if (mLifetime == DURATION) { mBubble.bFloatBob = true; } else if (mDestroyTime != 0) { if (mLifeTime - mDestroyTime < FADE_OUT_DURATION) { double ratio = 1 - double(mLifetime - mDestroyTime) / FADE_OUT_DURATION; // *= makes fading out faster than FADE_OUT_DURATION states. Oh welp. mBubble.alpha *= ratio; mPretender.a_setTranslucent(mPretender.alpha * ratio); } else { mPretender.destroy(); mBubble.destroy(); goAwayAndDie(); return; } } ++mLifetime; // These things have to be set all the time. mPretender.A_SetTics(int.Max); mPretender.bFloatBob = true; mPretender.A_SetRoll(mPretender.roll + mRoll); if (mOriginal) { mPretender.setOrigin(makePretenderPos(), true); mBubble .setOrigin(makeBubblePos(), true); } } private Vector3 makeBubblePos() { return mPretender.pos + (0, 0, mPretender.height * TARGET_SCALE / 2); } private Vector3 makePretenderPos() { return mOriginal.pos + (0, 0, 10); } const DURATION = TICRATE / 4; const FADE_OUT_DURATION = TICRATE * 60 * 3; const TARGET_ALPHA = 0.6; const TARGET_SCALE = 0.1; private Vector2 mOrigScale; private Vector2 mOrigBubbleScale; private int mLifetime; private int mDestroyTime; private int mOriginalRenderStyle; private double mOriginalAlpha; private Actor mBubble; private Actor mPretender; private Actor mOriginal; private double mRoll; }
6. Test
Basic check: the mod can be loaded without errors, and bubbling doesn't cause errors.
wait 2; map map01 wait 4; summon doomimp wait 6; mdk wait 9; quit