Jump to content
  • 0

Downgrading Zscript to Decorate: WaterfallFogSpawner


Desfar

Question

Posted (edited)

So I am working to make a few waterfall fog spawners, and trying to utilize the ZScript by Agent_Ash as a base.
However its proving to be insanely dense, and something I'm having trouble getting my head around.
Would someone be able to help me sort out whats happening in this behemoth?
Failing that, would anyone be able to point me to something which can serve as a decent ZScript primer for me to learn from?

ZScript by Agent_Ash contained below

Spoiler

version "4.10"

Class WaterfallFogSpawner : Actor
{
    const PARTSPAWNFREQ = 6;
    const VISCHECKTICS = 8;
    int user_rendermode;
    string user_color;
    enum ERenderMode
    {
        RM_Particles,
        RM_Actors,
    }

    double visDist;
    private int wwidth;
    private int trans;
    private color wcolor;
    private int wscale;
    private int wdensity;
    private int waterfallBits;
    private int partSpawnOfs;
    bool alwaysAnimate;
    array<TextureID> cachedFogTextures;

    static const name WFSWaterTranslations[] =
    {
        "", "RedWater", "GreenWater", "GoldWater", "PurpleWater", "OrangeWater", "WhiteWater", "BrownWater"
    };    

    static const name FogTextures[] = { "WFSPA0", "WFSPB0", "WFSPC0", "WFSPD0", "WFSPE0" };

    static const color FogColors[] = 
    { 
        "1010FF", //blue
        "FF2020", //red
        "AAFF00", //green
        "ffc320", //gold
        "FF20FF", //purple
        "FFCC00", //orange
        "FFFFFF", //white
        "422214"  //brown
    };

    Default
    {
        //$Title "Watefall fog spawner"
        //$Angled
        //$Category "Decoration"
        
        //$Arg0 "Length"
        //$Arg0Tooltip "Make this equal to the length of the linedef next to which the spawner is placed"
        
        //$Arg1 "Color"
        //$Arg1Type 11
        //$Arg1Enum { 0 = "Blue"; 1 = "Red"; 2 = "Green"; 3 = "Yellow"; 4 = "Purple"; 5 = "Dark orange"; 6 = "White"; 7 = "Brown"; }
        
        //$Arg2 "Scale"
        //$Arg2Type 11
        //$Arg2Enum { 1 = "x1"; 2 = "x2"; 3 = "x3"; 4 = "x4"; 5 = "x5"; 6 = "x6"; 7 = "x7"; 8 = "x8"; }
        
        //$Arg3 "Density"
        //$Arg3Tooltip "Determines the gap between water splash emitters.\nThe higher the number, the LESS dense the waterfall is.\nUsually 1 is OK, but you may use higher numbers if you use large scale.\n(0 is interpreted as 1.)"
        
        //$Arg4 "Ignore distance/visibility checks"
        //$Arg4Type 11
        //$Arg4Tooltip "If true, will animate regardless of visibility or distance to the player.\nUse with caution! May cause performance issues with many waterfalls."
        //$Arg4Enum { 0 = "False"; 2 = "True"; }
        
        +NOINTERACTION
        +NOBLOCKMAP
        +MOVEWITHSECTOR
        +SYNCHRONIZED
        +DONTBLAST
        FloatBobPhase 0;
        radius 8;
        height 16;
    }

    static clearscope double LinearMap(double val, double source_min, double source_max, double out_min, double out_max, bool clampit = false) 
    {
        double d = (val - source_min) * (out_max - out_min) / (source_max - source_min) + out_min;
        if (clampit)
        {
            double truemax = out_max > out_min ? out_max : out_min;
            double truemin = out_max > out_min ? out_min : out_max;
            d = Clamp(d, truemin, truemax);
        }
        return d;
    }

    /*    Built-in CheckSight() has an RNG call if the
        dest actor has 0 alpha of bINVISIBLE, which can 
        potentially desync. Boondorl helped me to make
        this simplified version without RNG:
    */
    bool SimpleCheckSight(PlayerPawn who)
    {
        if (!who)
            return false;
        //Get a vector from the *top* of the waterfall to player eye level:
        Vector3 delta = Vec3To(who) + (0,0,who.player.viewz - who.pos.z - height);
        vector2 aim = (VectorAngle(delta.x, delta.y), -VectorAngle(delta.xy.Length(), delta.z) );
        return !LineTrace(aim.x,delta.Length(),aim.y,TRF_THRUBLOCK|TRF_THRUHITSCAN|TRF_THRUACTORS, offsetz:height);
    }
    
    
    /*     This checks for distance to consoleplayer;
        should be safe for MP since the actor is 
        completely non-interactive.
        Returns 0 if sight check fails.
    */
    double CheckPlayerVisibility()
    {
        // If this is true, ignore sight check and always return
        // the smallest sensible value
        if (alwaysAnimate)
        {
            return 256;
        }
        PlayerInfo player = players[consoleplayer];
        if (player && player.mo && SimpleCheckSight(player.mo))
        {
            return Distance3D(player.mo);
        }
        return 0;
    }

    override void PostBeginPlay()
    {
        super.PostBeginPlay();

        //preserve flags set in the map editor:
        bDORMANT = (SpawnFlags & MTF_DORMANT);
        //since NOGRAVITY can't be set from the editor, we'll use the standstill flag instead:
        bNOGRAVITY = (SpawnFlags & MTF_STANDSTILL);
        //argument #1: waterfall width (length):
        wwidth = Clamp(args[0], 1, 1024); 
        //argument #2: waterfall color (blue by default):
        trans = Clamp(args[1], 0, min(WFSWaterTranslations.Size()-1, FogColors.Size()-1));
        //argument #3: scale of waterfall particles:
        wscale = Clamp(args[2], 1, 7);
        //argument #4: density of particles:
        wdensity = Clamp(args[3], 1, wwidth);
        //argument #5: skip distance check if not 0:
        alwaysAnimate = Clamp(args[4], 0, 1);

        if (user_color)
        {
            wcolor = color(user_color);
        }
        
        //get total number of particles by dividing width by density:
        waterfallBits = wwidth / wdensity;
        double startOfs = -(wwidth / 2);

        for (int i = 0; i < FogTextures.Size() - 1; i++)
        {
            cachedFogTextures.Push(int(TexMan.CheckForTexture(FogTextures)));
        }

        if (user_rendermode != RM_Actors)
        {
            return;
        }
        
        for (int steps = waterfallBits; steps >= 0; steps--)
        {
            let fog = WaterfallFog(Spawn("WaterfallFog",pos));
            if (!fog)
                continue;
            
            fog.A_SetTranslation(WFSWaterTranslations[trans]);
            vector2 sscale = 0.15 * (wscale,wscale);
            fog.scale = sscale;
            fog.wscale = sscale;
            vector3 fogvel = (frandom[fog](-0.8,0.8),frandom[fog](-0.8,0.8),frandom[fog](0.6,1.4));
            fog.vel = fogvel;
            fog.spawnVel = fogvel;
            fog.Warp(self, yofs: startOfs + (steps * wdensity));
            fog.spawnPos = fog.pos;
            fog.wspawner = WaterfallFogSpawner(self);
            if (wcolor != 0)
            {
                fog.A_SetRenderstyle(fog.alpha, STYLE_Shaded);
                fog.SetShade(wcolor);
            }
            
            //    Forcefully make the particles "tick" a few times
            //    so that they don't appear/reappear at the same time.
            //    (In a way this is a fancier version of +RANDOMIZE.)
            for (int fogtics = random[fogtics](0,32); fogtics > 0; fogtics--)
            {
                fog.AnimateFog();
            }
        }
    }

    override void Activate(Actor activator)
    {
        Super.Activate(activator);
        bDORMANT = false;
    }

    override void Deactivate(Actor activator)
    {
        Super.Deactivate(activator);
        bDORMANT = true;
    }

    override void Tick()
    {
        if (bDORMANT)
            return;
        
        if (!bNOGRAVITY)
        {
            SetOrigin((pos.xy, floorz), true);
        }
        
        /*    Re-checking visibility/distance every tic is 
            excessive, so we only do it every VISCHECKTICS tics.
        */
        if (GetAge() % VISCHECKTICS == 0)
        {
            visDist = CheckPlayerVisibility();
        }

        if (user_rendermode != RM_Particles || visDist <= 0)
        {
            return;
        }

        FSpawnParticleParams fog;
        fog.lifetime = 40;
        let def = GetDefaultByType('WaterfallFog');
        fog.startalpha = def.alpha;
        fog.fadestep = -1;
        fog.accel.z = -(def.gravity);
        double fogScale = wscale * 0.15;
        if (wcolor != 0)
        {
            fog.color1 = wcolor;
            fog.style = STYLE_Shaded;
        }
        else
        {
            fog.color1 = FogColors[trans];
        }
        fog.flags = SPF_ROLL|SPF_REPLACE;

        double startOfs = -(wwidth / 2);
        for (int steps = partSpawnOfs; steps <= waterfallBits; steps += PARTSPAWNFREQ)
        {
            fog.texture = cachedFogTextures[random[fog](0, cachedFogTextures.Size()-1)];
            fog.size = TexMan.GetSize(fog.texture) * fogScale * frandom[fog](0.8, 1.2);
            fog.sizestep = fog.size * 0.03;
            fog.vel = (frandom[fog](-0.8,0.8),frandom[fog](-0.8,0.8),frandom[fog](0.6,1.4));
            
            fog.pos.xy = (0, startOfs + (steps * wdensity));
            fog.pos.xy = self.pos.xy + Actor.RotateVector(fog.pos.xy, angle);
            fog.pos.z = self.pos.z;

            double ofs = fog.size * fogScale;
            fog.pos += (frandom[fog](-ofs, ofs), frandom[fog](-ofs, ofs), frandom[fog](0, ofs));

            fog.startroll = frandom[fog](-40,40);
            fog.rollvel = frandom[fog](-2.4,2.4);
            
            Level.SpawnParticle(fog);
        }
        partSpawnOfs++;
        if (partSpawnOfs >= PARTSPAWNFREQ)
        {
            partSpawnOfs = 0;
        }
    }
}

Class WaterfallFog : Actor
{
    const FOGFADEFAC = 0.013;

    vector3 spawnPos;
    vector3 spawnVel;
    vector2 wscale;
    double wroll;
    int doTic;
    WaterfallFogSpawner wspawner;

    Default
    {
        radius 1;
        height 1;
        renderstyle 'Translucent';
        alpha 0.32;
        gravity 0.15;
        FloatBobPhase 0;
        +ROLLSPRITE
        +FORCEXYBILLBOARD
        +NOINTERACTION
        +NOBLOCKMAP
        +SYNCHRONIZED
        +DONTBLAST
    }

    void AnimateFog()
    {
        vel.z -= gravity;
        SetOrigin(pos+vel,true);
        scale *= 1.03;
        A_FadeOut(FOGFADEFAC, 0);
        roll += wroll;
        if (alpha <= 0) {
            SetOrigin(spawnPos,false);
            vel = spawnVel;
            vel.x *= randompick[fog](-1,1);
            vel.y *= randompick[fog](-1,1);
            scale = wscale;
            alpha = default.alpha;
            roll = default.roll;
        }
    }

    override void PostBeginPlay()
    {
        super.PostBeginPlay();
        roll = frandom[fog](-40,40);
        frame = random[fog](0,4);
        wroll = frandom[fog](-2.4,2.4);
        doTic = 1;
    }    

    override void Tick()
    {
        if (!wspawner)
        {
            Destroy();
            return;
        }
        if (wspawner.bDORMANT || (!wspawner.alwaysAnimate && wspawner.visDist <= 0))
        {
            return;
        }
        if (!wspawner.bNOGRAVITY)
        {
            spawnPos.z = wspawner.pos.z;
        }
        //the animation will be executed less often for every 256 units the player is away from the waterfall:
        doTic = Clamp(wspawner.visDist / 256, 1, 80);
        //console.printf ("age %d | dotic: %d | Distance %d",GetAge(),dotic,wspawner.visDist);
        if (GetAge() % doTic == 0)
        {
            AnimateFog();
        }
    }

    States {
    Spawn:
        WFSP # -1;
        stop;
    }
}


 

Edited by Desfar

Share this post


Link to post

6 answers to this question

Recommended Posts

  • 2

Sorry, but this is clearly impossible to do in pure decorate. Most of the spawners actions is done through modification of Tick(), which is inaccessible in zscript.

Share this post


Link to post
  • 0

Jind of figured I couldn't make an exact copy, and was aiming for more of a "great value" version of the code.

Even if it was just a singular map point that would spawn the froth in a small area requiring several points to emulate it.

 

Share this post


Link to post
  • 0
Posted (edited)

You still dont understand that even the 'simple actor' is written in zscript and all the fancy actions happens by modificating things, that are accessible to zscript only.

The waterfallfog actor uses new custom function AnimateFog() (decorate allows anonymous functions, but not THIS level of coding. Impossible.), then it modifies PostBeginPlay() and Tick(). Both PostBeginPlay() and Tick() are inaccessible in decorate.

 

So, even if you toook out the waterfallfog actor's spawn state, you will see only static, not animated actor. Because teh animation itself is defined in zscript. I know what I talk about, because I use this spawner.

 

Edit: thr functionality of the waterfall effect is made in the way that the animation is running only when player looks at said actor and the actor could be actually seen by player. When player looks away, or gets to place where he cannot see the waterfall, the animation is stopped. This functionality cannot be replicated either in decorate, or with acs support, because either decorqte or acs doesnt have access to such information. Zscript has. So the only option is to use zscript. If you want a word of advice, just start learning zscript. It's little bit harder that decorate, but once you get the basics, you'll find that it allows you to easily make really fancy stuff, like this waterfall fog effect.

 

Edited by fekete

Share this post


Link to post
  • 0
Posted (edited)

Edit for anyone who is looking for the chimp version of the waterfallmist thingie, I got one jury rigged up.
Currently adding some of the fine tuning such as distance based generation, but it works decently.

(lower picture still using sprites by the OG guy)
HMU if wanted.

Waterfall.png

Edited by Desfar

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Answer this question...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...