From 6eb901530345294f2836e60b5a7f60503728d59b Mon Sep 17 00:00:00 2001 From: Edoardo Prezioso Date: Fri, 13 Jan 2017 14:51:31 +0100 Subject: [PATCH 01/10] - Fixed GCC/Clang compile errors. --- src/gl/scene/gl_bsp.cpp | 1 + src/p_sectors.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gl/scene/gl_bsp.cpp b/src/gl/scene/gl_bsp.cpp index af4d46d61..9b78850a9 100644 --- a/src/gl/scene/gl_bsp.cpp +++ b/src/gl/scene/gl_bsp.cpp @@ -28,6 +28,7 @@ #include "p_lnspec.h" #include "p_local.h" #include "a_sharedglobal.h" +#include "g_levellocals.h" #include "r_sky.h" #include "p_effect.h" #include "po_man.h" diff --git a/src/p_sectors.cpp b/src/p_sectors.cpp index e64296da7..5788588a6 100644 --- a/src/p_sectors.cpp +++ b/src/p_sectors.cpp @@ -1464,7 +1464,7 @@ DEFINE_ACTION_FUNCTION(_Sector, NextLowestFloorAt) IFVIRTUALPTRNAME(act, "SectorAction", TriggerAction) { - VMValue params[3] = { act, thing, activation }; + VMValue params[3] = { (DObject *)act, thing, activation }; VMReturn ret; int didit; ret.IntAt(&didit); From 8e7e1ed757df50d72376993d99daabb290df1654 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 13 Jan 2017 15:44:16 +0100 Subject: [PATCH 02/10] - don't use local TObjPtr variables. --- src/p_sectors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_sectors.cpp b/src/p_sectors.cpp index 5788588a6..5c14d56a0 100644 --- a/src/p_sectors.cpp +++ b/src/p_sectors.cpp @@ -1455,7 +1455,7 @@ DEFINE_ACTION_FUNCTION(_Sector, NextLowestFloorAt) bool sector_t::TriggerSectorActions(AActor *thing, int activation) { - auto act = SecActTarget; + AActor *act = SecActTarget; bool res = false; while (act != nullptr) From 51cc7feb4c26110b0495944d5bb39b8cb2c00685 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 13 Jan 2017 19:29:54 +0100 Subject: [PATCH 03/10] - scriptified the particle fountains. --- src/CMakeLists.txt | 1 - src/actor.h | 5 +- src/g_shared/a_fountain.cpp | 70 ----------------------- src/p_effect.cpp | 6 +- src/p_effect.h | 10 ---- src/p_mobj.cpp | 2 + wadsrc/static/zscript/actor.txt | 1 + wadsrc/static/zscript/shared/fountain.txt | 46 ++++++++++++--- 8 files changed, 47 insertions(+), 94 deletions(-) delete mode 100644 src/g_shared/a_fountain.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7826ec6ab..e076529c9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1160,7 +1160,6 @@ set (PCH_SOURCES g_shared/a_action.cpp g_shared/a_decals.cpp g_shared/a_flashfader.cpp - g_shared/a_fountain.cpp g_shared/a_lightning.cpp g_shared/a_morph.cpp g_shared/a_movingcamera.cpp diff --git a/src/actor.h b/src/actor.h index 562984577..a764426e5 100644 --- a/src/actor.h +++ b/src/actor.h @@ -1014,12 +1014,13 @@ public: double FloatSpeed; int sprite; // used to find patch_t and flip value - BYTE frame; // sprite frame to draw + uint8_t frame; // sprite frame to draw + uint8_t effects; // [RH] see p_effect.h + uint8_t fountaincolor; // Split out of 'effect' to have easier access. DVector2 Scale; // Scaling values; 1 is normal size FRenderStyle RenderStyle; // Style to draw this actor with ActorRenderFlags renderflags; // Different rendering flags FTextureID picnum; // Draw this instead of sprite if valid - DWORD effects; // [RH] see p_effect.h double Alpha; // Since P_CheckSight makes an alpha check this can't be a float. It has to be a double. DWORD fillcolor; // Color to draw when STYLE_Shaded diff --git a/src/g_shared/a_fountain.cpp b/src/g_shared/a_fountain.cpp deleted file mode 100644 index 7d7e2e412..000000000 --- a/src/g_shared/a_fountain.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* -** a_fountain.cpp -** Actors that make particle fountains -** -**--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#include "actor.h" -#include "info.h" -#include "p_effect.h" -#include "doomdata.h" - -class AParticleFountain : public AActor -{ - DECLARE_CLASS (AParticleFountain, AActor) -public: - void PostBeginPlay (); - void Activate (AActor *activator); - void Deactivate (AActor *activator); -}; - -IMPLEMENT_CLASS(AParticleFountain, false, false) - -void AParticleFountain::PostBeginPlay () -{ - Super::PostBeginPlay (); - if (!(SpawnFlags & MTF_DORMANT)) - CallActivate (NULL); -} - -void AParticleFountain::Activate (AActor *activator) -{ - Super::Activate (activator); - effects &= ~FX_FOUNTAINMASK; - effects |= health << FX_FOUNTAINSHIFT; -} - -void AParticleFountain::Deactivate (AActor *activator) -{ - Super::Deactivate (activator); - effects &= ~FX_FOUNTAINMASK; -} - diff --git a/src/p_effect.cpp b/src/p_effect.cpp index 0f784fa06..f6b54b34a 100644 --- a/src/p_effect.cpp +++ b/src/p_effect.cpp @@ -358,7 +358,7 @@ void P_RunEffects () while ( (actor = iterator.Next ()) ) { - if (actor->effects) + if (actor->effects || actor->fountaincolor) { // Only run the effect if the actor is potentially visible int rnum = pnum + actor->Sector->Index(); @@ -494,7 +494,7 @@ void P_RunEffect (AActor *actor, int effects) P_DrawSplash2 (6, pos, moveangle + 180, 2, 2); } - if (effects & FX_FOUNTAINMASK) + if (actor->fountaincolor) { // Particle fountain @@ -508,7 +508,7 @@ void P_RunEffect (AActor *actor, int effects) &black, &grey3, &grey4, &white }; - int color = (effects & FX_FOUNTAINMASK) >> 15; + int color = actor->fountaincolor*2; MakeFountain (actor, *fountainColors[color], *fountainColors[color+1]); } if (effects & FX_RESPAWNINVUL) diff --git a/src/p_effect.h b/src/p_effect.h index 4e8e73d09..6a9f5ccf9 100644 --- a/src/p_effect.h +++ b/src/p_effect.h @@ -38,16 +38,6 @@ #define FX_RESPAWNINVUL 0x00000020 #define FX_VISIBILITYPULSE 0x00000040 -#define FX_FOUNTAINMASK 0x00070000 -#define FX_FOUNTAINSHIFT 16 -#define FX_REDFOUNTAIN 0x00010000 -#define FX_GREENFOUNTAIN 0x00020000 -#define FX_BLUEFOUNTAIN 0x00030000 -#define FX_YELLOWFOUNTAIN 0x00040000 -#define FX_PURPLEFOUNTAIN 0x00050000 -#define FX_BLACKFOUNTAIN 0x00060000 -#define FX_WHITEFOUNTAIN 0x00070000 - struct subsector_t; // [RH] Particle details diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index c3a7ac424..128455a39 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -314,6 +314,7 @@ DEFINE_FIELD(AActor, MissileState) DEFINE_FIELD(AActor, ConversationRoot) DEFINE_FIELD(AActor, Conversation) DEFINE_FIELD(AActor, DecalGenerator) +DEFINE_FIELD(AActor, fountaincolor) DEFINE_FIELD(PClassActor, Obituary) DEFINE_FIELD(PClassActor, HitObituary) @@ -366,6 +367,7 @@ void AActor::Serialize(FSerializer &arc) A("lastlookpn", LastLookPlayerNumber) ("lastlookactor", LastLookActor) A("effects", effects) + A("fountaincolor", fountaincolor) A("alpha", Alpha) A("fillcolor", fillcolor) A("sector", Sector) diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 00d80c49c..8133b1ad2 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -182,6 +182,7 @@ class Actor : Thinker native native State MeleeState; native State MissileState; native voidptr /*DecalBase*/ DecalGenerator; + native uint8 fountaincolor; native meta String Obituary; // Player was killed by this actor native meta String HitObituary; // Player was killed by this actor in melee diff --git a/wadsrc/static/zscript/shared/fountain.txt b/wadsrc/static/zscript/shared/fountain.txt index 60129a8c5..02e42007c 100644 --- a/wadsrc/static/zscript/shared/fountain.txt +++ b/wadsrc/static/zscript/shared/fountain.txt @@ -1,5 +1,16 @@ -class ParticleFountain : Actor native +class ParticleFountain : Actor { + enum EColor + { + REDFOUNTAIN = 1, + GREENFOUNTAIN = 2, + BLUEFOUNTAIN = 3, + YELLOWFOUNTAIN = 4, + PURPLEFOUNTAIN = 5, + BLACKFOUNTAIN = 6, + WHITEFOUNTAIN = 7 + } + default { Height 0; @@ -7,13 +18,32 @@ class ParticleFountain : Actor native +NOGRAVITY +INVISIBLE } + + override void PostBeginPlay () + { + Super.PostBeginPlay (); + if (!(SpawnFlags & MTF_DORMANT)) + Activate (null); + } + + override void Activate (Actor activator) + { + Super.Activate (activator); + fountaincolor = health; + } + + override void Deactivate (Actor activator) + { + Super.Deactivate (activator); + fountaincolor = 0; + } } class RedParticleFountain : ParticleFountain { default { - Health 1; + Health REDFOUNTAIN; } } @@ -21,7 +51,7 @@ class GreenParticleFountain : ParticleFountain { default { - Health 2; + Health GREENFOUNTAIN; } } @@ -29,7 +59,7 @@ class BlueParticleFountain : ParticleFountain { default { - Health 3; + Health BLUEFOUNTAIN; } } @@ -37,7 +67,7 @@ class YellowParticleFountain : ParticleFountain { default { - Health 4; + Health YELLOWFOUNTAIN; } } @@ -45,7 +75,7 @@ class PurpleParticleFountain : ParticleFountain { default { - Health 5; + Health PURPLEFOUNTAIN; } } @@ -53,7 +83,7 @@ class BlackParticleFountain : ParticleFountain { default { - Health 6; + Health BLACKFOUNTAIN; } } @@ -61,6 +91,6 @@ class WhiteParticleFountain : ParticleFountain { default { - Health 7; + Health WHITEFOUNTAIN; } } From 4be0767d7b796c90a8301849eba957e077878c65 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 13 Jan 2017 22:13:03 +0100 Subject: [PATCH 04/10] - scriptified the moving camera. --- src/CMakeLists.txt | 1 - src/g_shared/a_movingcamera.cpp | 650 ------------------ src/p_maputl.cpp | 6 +- src/p_mobj.cpp | 7 + wadsrc/static/zscript/actor.txt | 11 +- wadsrc/static/zscript/shared/movingcamera.txt | 549 ++++++++++++++- 6 files changed, 562 insertions(+), 662 deletions(-) delete mode 100644 src/g_shared/a_movingcamera.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e076529c9..2fad79c63 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1162,7 +1162,6 @@ set (PCH_SOURCES g_shared/a_flashfader.cpp g_shared/a_lightning.cpp g_shared/a_morph.cpp - g_shared/a_movingcamera.cpp g_shared/a_quake.cpp g_shared/a_randomspawner.cpp g_shared/a_skies.cpp diff --git a/src/g_shared/a_movingcamera.cpp b/src/g_shared/a_movingcamera.cpp deleted file mode 100644 index f32550f2d..000000000 --- a/src/g_shared/a_movingcamera.cpp +++ /dev/null @@ -1,650 +0,0 @@ -/* -** a_movingcamera.cpp -** Cameras that move and related neat stuff -** -**--------------------------------------------------------------------------- -** Copyright 1998-2006 Randy Heit -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the author may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -**--------------------------------------------------------------------------- -** -*/ - -#include "actor.h" -#include "info.h" -#include "p_local.h" -#include "p_lnspec.h" -#include "doomstat.h" -#include "serializer.h" -#include "g_levellocals.h" - -/* -== InterpolationPoint: node along a camera's path -== -== args[0] = pitch -== args[1] = time (in octics) to get here from previous node -== args[2] = time (in octics) to stay here before moving to next node -== args[3] = low byte of next node's tid -== args[4] = high byte of next node's tid -*/ - -class AInterpolationPoint : public AActor -{ - DECLARE_CLASS (AInterpolationPoint, AActor) - HAS_OBJECT_POINTERS -public: - void BeginPlay (); - void HandleSpawnFlags (); - void Tick () {} // Nodes do no thinking - AInterpolationPoint *ScanForLoop (); - void FormChain (); - - - void Serialize(FSerializer &arc); - - TObjPtr Next; -}; - -IMPLEMENT_CLASS(AInterpolationPoint, false, true) - -IMPLEMENT_POINTERS_START(AInterpolationPoint) - IMPLEMENT_POINTER(Next) -IMPLEMENT_POINTERS_END - -void AInterpolationPoint::Serialize(FSerializer &arc) -{ - Super::Serialize (arc); - arc("next", Next); -} - -void AInterpolationPoint::BeginPlay () -{ - Super::BeginPlay (); - Next = NULL; -} - -void AInterpolationPoint::HandleSpawnFlags () -{ - // Spawn flags mean nothing to an interpolation point -} - -void AInterpolationPoint::FormChain () -{ - if (flags & MF_AMBUSH) - return; - - flags |= MF_AMBUSH; - - TActorIterator iterator (args[3] + 256 * args[4]); - Next = iterator.Next (); - - if (Next == this) // Don't link to self - Next = iterator.Next (); - - if (Next == NULL && (args[3] | args[4])) - Printf ("Can't find target for camera node %d\n", tid); - - Angles.Pitch = (double)clamp((signed char)args[0], -89, 89); - - if (Next != NULL) - Next->FormChain (); -} - -// Return the node (if any) where a path loops, relative to this one. -AInterpolationPoint *AInterpolationPoint::ScanForLoop () -{ - AInterpolationPoint *node = this; - while (node->Next && node->Next != this && node->special1 == 0) - { - node->special1 = 1; - node = node->Next; - } - return node->Next == this ? node : NULL; -} - -/* -== InterpolationSpecial: Holds a special to execute when a -== PathFollower reaches an InterpolationPoint of the same TID. -*/ - -class AInterpolationSpecial : public AActor -{ - DECLARE_CLASS (AInterpolationSpecial, AActor) -public: - void Tick () {} // Does absolutely nothing itself -}; - -IMPLEMENT_CLASS(AInterpolationSpecial, false, false) - -/* -== PathFollower: something that follows a camera path -== Base class for some moving cameras -== -== args[0] = low byte of first node in path's tid -== args[1] = high byte of first node's tid -== args[2] = bit 0 = follow a linear path (rather than curved) -== bit 1 = adjust angle -== bit 2 = adjust pitch -== bit 3 = aim in direction of motion -== -== Also uses: -== target = first node in path -== lastenemy = node prior to first node (if looped) -*/ - -class APathFollower : public AActor -{ - DECLARE_CLASS (APathFollower, AActor) - HAS_OBJECT_POINTERS -public: - void BeginPlay (); - void PostBeginPlay (); - void Tick (); - void Activate (AActor *activator); - void Deactivate (AActor *activator); -protected: - double Splerp (double p1, double p2, double p3, double p4); - double Lerp (double p1, double p2); - virtual bool Interpolate (); - virtual void NewNode (); - - - void Serialize(FSerializer &arc); - - bool bActive, bJustStepped; - TObjPtr PrevNode, CurrNode; - float Time; // Runs from 0.0 to 1.0 between CurrNode and CurrNode->Next - int HoldTime; -}; - -IMPLEMENT_CLASS(APathFollower, false, true) - -IMPLEMENT_POINTERS_START(APathFollower) - IMPLEMENT_POINTER(PrevNode) - IMPLEMENT_POINTER(CurrNode) -IMPLEMENT_POINTERS_END - -void APathFollower::Serialize(FSerializer &arc) -{ - Super::Serialize (arc); - arc("active", bActive) - ("juststepped", bJustStepped) - ("prevnode", PrevNode) - ("currnode", CurrNode) - ("time", Time) - ("holdtime", HoldTime); -} - -// Interpolate between p2 and p3 along a Catmull-Rom spline -// http://research.microsoft.com/~hollasch/cgindex/curves/catmull-rom.html -double APathFollower::Splerp (double p1, double p2, double p3, double p4) -{ - double t = Time; - double res = 2*p2; - res += (p3 - p1) * Time; - t *= Time; - res += (2*p1 - 5*p2 + 4*p3 - p4) * t; - t *= Time; - res += (3*p2 - 3*p3 + p4 - p1) * t; - return 0.5f * res; -} - -// Linearly interpolate between p1 and p2 -double APathFollower::Lerp (double p1, double p2) -{ - return p1 + Time * (p2 - p1); -} - -void APathFollower::BeginPlay () -{ - Super::BeginPlay (); - PrevNode = CurrNode = NULL; - bActive = false; -} - -void APathFollower::PostBeginPlay () -{ - // Find first node of path - TActorIterator iterator (args[0] + 256 * args[1]); - AInterpolationPoint *node = iterator.Next (); - AInterpolationPoint *prevnode; - - target = node; - - if (node == NULL) - { - Printf ("PathFollower %d: Can't find interpolation pt %d\n", - tid, args[0] + 256 * args[1]); - return; - } - - // Verify the path has enough nodes - node->FormChain (); - if (args[2] & 1) - { // linear path; need 2 nodes - if (node->Next == NULL) - { - Printf ("PathFollower %d: Path needs at least 2 nodes\n", tid); - return; - } - lastenemy = NULL; - } - else - { // spline path; need 4 nodes - if (node->Next == NULL || - node->Next->Next == NULL || - node->Next->Next->Next == NULL) - { - Printf ("PathFollower %d: Path needs at least 4 nodes\n", tid); - return; - } - // If the first node is in a loop, we can start there. - // Otherwise, we need to start at the second node in the path. - prevnode = node->ScanForLoop (); - if (prevnode == NULL || prevnode->Next != node) - { - lastenemy = target; - target = node->Next; - } - else - { - lastenemy = prevnode; - } - } -} - -void APathFollower::Deactivate (AActor *activator) -{ - bActive = false; -} - -void APathFollower::Activate (AActor *activator) -{ - if (!bActive) - { - CurrNode = barrier_cast(target); - PrevNode = barrier_cast(lastenemy); - - if (CurrNode != NULL) - { - NewNode (); - SetOrigin (CurrNode->Pos(), false); - Time = 0.f; - HoldTime = 0; - bJustStepped = true; - bActive = true; - } - } -} - -void APathFollower::Tick () -{ - if (!bActive) - return; - - if (bJustStepped) - { - bJustStepped = false; - if (CurrNode->args[2]) - { - HoldTime = level.time + CurrNode->args[2] * TICRATE / 8; - SetXYZ(CurrNode->Pos()); - } - } - - if (HoldTime > level.time) - return; - - // Splines must have a previous node. - if (PrevNode == NULL && !(args[2] & 1)) - { - bActive = false; - return; - } - - // All paths must have a current node. - if (CurrNode->Next == NULL) - { - bActive = false; - return; - } - - if (Interpolate ()) - { - Time += float(8.f / ((double)CurrNode->args[1] * (double)TICRATE)); - if (Time > 1.f) - { - Time -= 1.f; - bJustStepped = true; - PrevNode = CurrNode; - CurrNode = CurrNode->Next; - if (CurrNode != NULL) - NewNode (); - if (CurrNode == NULL || CurrNode->Next == NULL) - CallDeactivate (this); - if ((args[2] & 1) == 0 && CurrNode->Next->Next == NULL) - CallDeactivate (this); - } - } -} - -void APathFollower::NewNode () -{ - TActorIterator iterator (CurrNode->tid); - AInterpolationSpecial *spec; - - while ( (spec = iterator.Next ()) ) - { - P_ExecuteSpecial(spec->special, NULL, NULL, false, spec->args[0], - spec->args[1], spec->args[2], spec->args[3], spec->args[4]); - } -} - -bool APathFollower::Interpolate () -{ - DVector3 dpos(0, 0, 0); - FLinkContext ctx; - - if ((args[2] & 8) && Time > 0.f) - { - dpos = Pos(); - } - - if (CurrNode->Next==NULL) return false; - - UnlinkFromWorld (&ctx); - DVector3 newpos; - if (args[2] & 1) - { // linear - newpos.X = Lerp(CurrNode->X(), CurrNode->Next->X()); - newpos.Y = Lerp(CurrNode->Y(), CurrNode->Next->Y()); - newpos.Z = Lerp(CurrNode->Z(), CurrNode->Next->Z()); - } - else - { // spline - if (CurrNode->Next->Next==NULL) return false; - - newpos.X = Splerp(PrevNode->X(), CurrNode->X(), CurrNode->Next->X(), CurrNode->Next->Next->X()); - newpos.Y = Splerp(PrevNode->Y(), CurrNode->Y(), CurrNode->Next->Y(), CurrNode->Next->Next->Y()); - newpos.Z = Splerp(PrevNode->Z(), CurrNode->Z(), CurrNode->Next->Z(), CurrNode->Next->Next->Z()); - } - SetXYZ(newpos); - LinkToWorld (&ctx); - - if (args[2] & 6) - { - if (args[2] & 8) - { - if (args[2] & 1) - { // linear - dpos.X = CurrNode->Next->X() - CurrNode->X(); - dpos.Y = CurrNode->Next->Y() - CurrNode->Y(); - dpos.Z = CurrNode->Next->Z() - CurrNode->Z(); - } - else if (Time > 0.f) - { // spline - dpos = newpos - dpos; - } - else - { - int realarg = args[2]; - args[2] &= ~(2|4|8); - Time += 0.1f; - dpos = newpos; - Interpolate (); - Time -= 0.1f; - args[2] = realarg; - dpos = newpos - dpos; - newpos -= dpos; - SetXYZ(newpos); - } - if (args[2] & 2) - { // adjust yaw - Angles.Yaw = dpos.Angle(); - } - if (args[2] & 4) - { // adjust pitch; use floats for precision - double dist = dpos.XY().Length(); - Angles.Pitch = dist != 0.f ? VecToAngle(dist, -dpos.Z) : 0.; - } - } - else - { - if (args[2] & 2) - { // interpolate angle - DAngle angle1 = CurrNode->Angles.Yaw.Normalized180(); - DAngle angle2 = angle1 + deltaangle(angle1, CurrNode->Next->Angles.Yaw); - Angles.Yaw = Lerp(angle1.Degrees, angle2.Degrees); - } - if (args[2] & 1) - { // linear - if (args[2] & 4) - { // interpolate pitch - Angles.Pitch = Lerp(CurrNode->Angles.Pitch.Degrees, CurrNode->Next->Angles.Pitch.Degrees); - } - } - else - { // spline - if (args[2] & 4) - { // interpolate pitch - Angles.Pitch = Splerp(PrevNode->Angles.Pitch.Degrees, CurrNode->Angles.Pitch.Degrees, - CurrNode->Next->Angles.Pitch.Degrees, CurrNode->Next->Next->Angles.Pitch.Degrees); - } - } - } - } - - return true; -} - -/* -== ActorMover: Moves any actor along a camera path -== -== Same as PathFollower, except -== args[2], bit 7: make nonsolid -== args[3] = tid of thing to move -== -== also uses: -== tracer = thing to move -*/ - -class AActorMover : public APathFollower -{ - DECLARE_CLASS (AActorMover, APathFollower) -public: - void BeginPlay(); - void PostBeginPlay (); - void Activate (AActor *activator); - void Deactivate (AActor *activator); -protected: - bool Interpolate (); -}; - -IMPLEMENT_CLASS(AActorMover, false, false) - -void AActorMover::BeginPlay() -{ - ChangeStatNum(STAT_ACTORMOVER); -} - -void AActorMover::PostBeginPlay () -{ - Super::PostBeginPlay (); - - TActorIterator iterator (args[3]); - tracer = iterator.Next (); - - if (tracer == NULL) - { - Printf ("ActorMover %d: Can't find target %d\n", tid, args[3]); - } - else - { - special1 = tracer->flags; - special2 = tracer->flags2; - } -} - -bool AActorMover::Interpolate () -{ - if (tracer == NULL) - return true; - - if (Super::Interpolate ()) - { - double savedz = tracer->Z(); - tracer->SetZ(Z()); - if (!P_TryMove (tracer, Pos(), true)) - { - tracer->SetZ(savedz); - return false; - } - - if (args[2] & 2) - tracer->Angles.Yaw = Angles.Yaw; - if (args[2] & 4) - tracer->Angles.Pitch = Angles.Pitch; - - return true; - } - return false; -} - -void AActorMover::Activate (AActor *activator) -{ - if (tracer == NULL || bActive) - return; - - Super::Activate (activator); - special1 = tracer->flags; - special2 = tracer->flags2; - tracer->flags |= MF_NOGRAVITY; - if (args[2] & 128) - { - FLinkContext ctx; - tracer->UnlinkFromWorld (&ctx); - tracer->flags |= MF_NOBLOCKMAP; - tracer->flags &= ~MF_SOLID; - tracer->LinkToWorld (&ctx); - } - if (tracer->flags3 & MF3_ISMONSTER) - { - tracer->flags2 |= MF2_INVULNERABLE | MF2_DORMANT; - } - // Don't let the renderer interpolate between the actor's - // old position and its new position. - Interpolate (); - tracer->ClearInterpolation(); -} - -void AActorMover::Deactivate (AActor *activator) -{ - if (bActive) - { - Super::Deactivate (activator); - if (tracer != NULL) - { - FLinkContext ctx; - tracer->UnlinkFromWorld (&ctx); - tracer->flags = ActorFlags::FromInt (special1); - tracer->LinkToWorld (&ctx); - tracer->flags2 = ActorFlags2::FromInt (special2); - } - } -} - -/* -== MovingCamera: Moves any actor along a camera path -== -== Same as PathFollower, except -== args[3] = tid of thing to look at (0 if none) -== -== Also uses: -== tracer = thing to look at -*/ - -class AMovingCamera : public APathFollower -{ - DECLARE_CLASS (AMovingCamera, APathFollower) - HAS_OBJECT_POINTERS -public: - void PostBeginPlay (); - - - void Serialize(FSerializer &arc); -protected: - bool Interpolate (); - - TObjPtr Activator; -}; - -IMPLEMENT_CLASS(AMovingCamera, false, true) - -IMPLEMENT_POINTERS_START(AMovingCamera) - IMPLEMENT_POINTER(Activator) -IMPLEMENT_POINTERS_END - -void AMovingCamera::Serialize(FSerializer &arc) -{ - Super::Serialize (arc); - arc("activator", Activator); -} - -void AMovingCamera::PostBeginPlay () -{ - Super::PostBeginPlay (); - - Activator = NULL; - if (args[3] != 0) - { - TActorIterator iterator (args[3]); - tracer = iterator.Next (); - if (tracer == NULL) - { - Printf ("MovingCamera %d: Can't find thing %d\n", tid, args[3]); - } - } -} - -bool AMovingCamera::Interpolate () -{ - if (tracer == NULL) - return Super::Interpolate (); - - if (Super::Interpolate ()) - { - Angles.Yaw = AngleTo(tracer, true); - - if (args[2] & 4) - { // Also aim camera's pitch; - DVector3 diff = Pos() - tracer->PosPlusZ(tracer->Height / 2); - double dist = diff.XY().Length(); - Angles.Pitch = dist != 0.f ? VecToAngle(dist, diff.Z) : 0.; - } - - return true; - } - return false; -} diff --git a/src/p_maputl.cpp b/src/p_maputl.cpp index e6b2aebea..ea2434b25 100644 --- a/src/p_maputl.cpp +++ b/src/p_maputl.cpp @@ -396,7 +396,8 @@ bool AActor::FixMapthingPos() DEFINE_ACTION_FUNCTION(AActor, UnlinkFromWorld) { PARAM_SELF_PROLOGUE(AActor); - self->UnlinkFromWorld(nullptr); // fixme + PARAM_POINTER_DEF(ctx, FLinkContext); + self->UnlinkFromWorld(ctx); // fixme return 0; } @@ -530,7 +531,8 @@ void AActor::LinkToWorld(FLinkContext *ctx, bool spawningmapthing, sector_t *sec DEFINE_ACTION_FUNCTION(AActor, LinkToWorld) { PARAM_SELF_PROLOGUE(AActor); - self->LinkToWorld(nullptr); // fixme + PARAM_POINTER_DEF(ctx, FLinkContext); + self->LinkToWorld(ctx); return 0; } diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 128455a39..22eae11a1 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -7754,6 +7754,13 @@ DEFINE_ACTION_FUNCTION(AActor, RotateVector) ACTION_RETURN_VEC2(DVector2(x, y).Rotated(angle)); } +DEFINE_ACTION_FUNCTION(AActor, Normalize180) +{ + PARAM_PROLOGUE; + PARAM_ANGLE(angle); + ACTION_RETURN_FLOAT(angle.Normalized180().Degrees); +} + DEFINE_ACTION_FUNCTION(AActor, DistanceBySpeed) { PARAM_SELF_PROLOGUE(AActor); diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 8133b1ad2..587fcf6de 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -28,6 +28,12 @@ struct FCheckPosition native void ClearLastRipped(); } +struct LinkContext +{ + voidptr sector_list; // really msecnode but that's not exported yet. + voidptr render_list; +} + class Actor : Thinker native { @@ -282,6 +288,7 @@ class Actor : Thinker native native static double absangle(double ang1, double ang2); native static Vector2 AngleToVector(double angle, double length = 1); native static Vector2 RotateVector(Vector2 vec, double angle); + native static double Normalize180(double ang); bool IsPointerEqual(int ptr_select1, int ptr_select2) @@ -433,8 +440,8 @@ class Actor : Thinker native native state FindState(statelabel st, bool exact = false); bool SetStateLabel(statelabel st, bool nofunction = false) { return SetState(FindState(st), nofunction); } native action state ResolveState(statelabel st); // this one, unlike FindState, is context aware. - native void LinkToWorld(); - native void UnlinkFromWorld(); + native void LinkToWorld(LinkContext ctx = null); + native void UnlinkFromWorld(out LinkContext ctx = null); native bool CanSeek(Actor target); native double AngleTo(Actor target, bool absolute = false); native void AddZ(double zadd, bool moving = true); diff --git a/wadsrc/static/zscript/shared/movingcamera.txt b/wadsrc/static/zscript/shared/movingcamera.txt index c5b8534db..0fc50f3e2 100644 --- a/wadsrc/static/zscript/shared/movingcamera.txt +++ b/wadsrc/static/zscript/shared/movingcamera.txt @@ -1,5 +1,51 @@ -class InterpolationPoint : Actor native +/* +** a_movingcamera.cpp +** Cameras that move and related neat stuff +** +**--------------------------------------------------------------------------- +** Copyright 1998-2016 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, self list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, self list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from self software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +/* +== InterpolationPoint: node along a camera's path +== +== args[0] = pitch +== args[1] = time (in octics) to get here from previous node +== args[2] = time (in octics) to stay here before moving to next node +== args[3] = low byte of next node's tid +== args[4] = high byte of next node's tid +*/ +class InterpolationPoint : Actor { + + InterpolationPoint Next; + default { +NOBLOCKMAP @@ -7,9 +53,61 @@ class InterpolationPoint : Actor native +DONTSPLASH RenderStyle "None"; } + + override void BeginPlay () + { + Super.BeginPlay (); + Next = null; + } + + void FormChain () + { + let me = self; + + while (me != null) + { + if (me.bAmbush) return; + + me.bAmbush = true; + + let iterator = ActorIterator.Create(me.args[3] + 256 * me.args[4], "InterpolationPoint"); + me.Next = InterpolationPoint(iterator.Next ()); + + if (me.Next == me) // Don't link to self + me.Next = InterpolationPoint(iterator.Next ()); + + int pt = (me.args[0] << 24) >> 24; // this is for truncating the value to a byte, presumably because some old WAD needs it... + me.Pitch = clamp(pt, -89, 89); + + if (me.Next == null && (me.args[3] | me.args[4])) + { + A_Log("Can't find target for camera node " .. me.tid); + } + + me = me.Next; + } + } + + // Return the node (if any) where a path loops, relative to self one. + InterpolationPoint ScanForLoop () + { + InterpolationPoint node = self; + while (node.Next && node.Next != self && node.special1 == 0) + { + node.special1 = 1; + node = node.Next; + } + return node.Next == self ? node : null; + } + } -class InterpolationSpecial : Actor native +/* +== InterpolationSpecial: Holds a special to execute when a +== PathFollower reaches an InterpolationPoint of the same TID. +*/ + +class InterpolationSpecial : Actor { default { @@ -18,9 +116,28 @@ class InterpolationSpecial : Actor native +NOGRAVITY +DONTSPLASH } + + override void Tick () {} // Does absolutely nothing itself + } -class PathFollower : Actor native +/* +== PathFollower: something that follows a camera path +== Base class for some moving cameras +== +== args[0] = low byte of first node in path's tid +== args[1] = high byte of first node's tid +== args[2] = bit 0 = follow a linear path (rather than curved) +== bit 1 = adjust angle +== bit 2 = adjust pitch +== bit 3 = aim in direction of motion +== +== Also uses: +== target = first node in path +== lastenemy = node prior to first node (if looped) +*/ + +class PathFollower : Actor { default { @@ -29,18 +146,436 @@ class PathFollower : Actor native +NOGRAVITY +DONTSPLASH } + + bool bActive, bJustStepped; + InterpolationPoint PrevNode, CurrNode; + double Time; // Runs from 0.0 to 1.0 between CurrNode and CurrNode.Next + int HoldTime; + + // Interpolate between p2 and p3 along a Catmull-Rom spline + // http://research.microsoft.com/~hollasch/cgindex/curves/catmull-rom.html + double Splerp (double p1, double p2, double p3, double p4) + { + double t = Time; + double res = 2*p2; + res += (p3 - p1) * Time; + t *= Time; + res += (2*p1 - 5*p2 + 4*p3 - p4) * t; + t *= Time; + res += (3*p2 - 3*p3 + p4 - p1) * t; + return 0.5 * res; + } + + // Linearly interpolate between p1 and p2 + double Lerp (double p1, double p2) + { + return p1 + Time * (p2 - p1); + } + + override void BeginPlay () + { + Super.BeginPlay (); + PrevNode = CurrNode = null; + bActive = false; + } + + override void PostBeginPlay () + { + // Find first node of path + let iterator = ActorIterator.Create(args[0] + 256 * args[1], "InterpolationPoint"); + let node = InterpolationPoint(iterator.Next ()); + InterpolationPoint prevnode; + + target = node; + + if (node == null) + { + A_Log ("PathFollower " .. tid .. ": Can't find interpolation pt " .. args[0] + 256 * args[1] .. "%d\n"); + return; + } + + // Verify the path has enough nodes + node.FormChain (); + if (args[2] & 1) + { // linear path; need 2 nodes + if (node.Next == null) + { + A_Log ("PathFollower " .. tid .. ": Path needs at least 2 nodes\n"); + return; + } + lastenemy = null; + } + else + { // spline path; need 4 nodes + if (node.Next == null || + node.Next.Next == null || + node.Next.Next.Next == null) + { + A_Log ("PathFollower " .. tid .. ": Path needs at least 4 nodes\n"); + return; + } + // If the first node is in a loop, we can start there. + // Otherwise, we need to start at the second node in the path. + prevnode = node.ScanForLoop (); + if (prevnode == null || prevnode.Next != node) + { + lastenemy = target; + target = node.Next; + } + else + { + lastenemy = prevnode; + } + } + } + + override void Deactivate (Actor activator) + { + bActive = false; + } + + override void Activate (Actor activator) + { + if (!bActive) + { + CurrNode = InterpolationPoint(target); + PrevNode = InterpolationPoint(lastenemy); + + if (CurrNode != null) + { + NewNode (); + SetOrigin (CurrNode.Pos, false); + Time = 0.; + HoldTime = 0; + bJustStepped = true; + bActive = true; + } + } + } + + override void Tick () + { + if (!bActive) + return; + + if (bJustStepped) + { + bJustStepped = false; + if (CurrNode.args[2]) + { + HoldTime = level.time + CurrNode.args[2] * TICRATE / 8; + SetXYZ(CurrNode.Pos); + } + } + + if (HoldTime > level.time) + return; + + // Splines must have a previous node. + if (PrevNode == null && !(args[2] & 1)) + { + bActive = false; + return; + } + + // All paths must have a current node. + if (CurrNode.Next == null) + { + bActive = false; + return; + } + + if (Interpolate ()) + { + Time += (8. / (CurrNode.args[1] * TICRATE)); + if (Time > 1.) + { + Time -= 1.; + bJustStepped = true; + PrevNode = CurrNode; + CurrNode = CurrNode.Next; + if (CurrNode != null) + NewNode (); + if (CurrNode == null || CurrNode.Next == null) + Deactivate (self); + if ((args[2] & 1) == 0 && CurrNode.Next.Next == null) + Deactivate (self); + } + } + } + + void NewNode () + { + let iterator = ActorIterator.Create(CurrNode.tid, "InterpolationSpecial"); + InterpolationSpecial spec; + + while ( (spec = InterpolationSpecial(iterator.Next ())) ) + { + A_CallSpecial(spec.special, spec.args[0], spec.args[1], spec.args[2], spec.args[3], spec.args[4]); + } + } + + virtual bool Interpolate () + { + Vector3 dpos = (0, 0, 0); + LinkContext ctx; + + if ((args[2] & 8) && Time > 0) + { + dpos = Pos; + } + + if (CurrNode.Next==null) return false; + + UnlinkFromWorld (ctx); + Vector3 newpos; + if (args[2] & 1) + { // linear + newpos.X = Lerp(CurrNode.pos.X, CurrNode.Next.pos.X); + newpos.Y = Lerp(CurrNode.pos.Y, CurrNode.Next.pos.Y); + newpos.Z = Lerp(CurrNode.pos.Z, CurrNode.Next.pos.Z); + } + else + { // spline + if (CurrNode.Next.Next==null) return false; + + newpos.X = Splerp(PrevNode.pos.X, CurrNode.pos.X, CurrNode.Next.pos.X, CurrNode.Next.Next.pos.X); + newpos.Y = Splerp(PrevNode.pos.Y, CurrNode.pos.Y, CurrNode.Next.pos.Y, CurrNode.Next.Next.pos.Y); + newpos.Z = Splerp(PrevNode.pos.Z, CurrNode.pos.Z, CurrNode.Next.pos.Z, CurrNode.Next.Next.pos.Z); + } + SetXYZ(newpos); + LinkToWorld (ctx); + + if (args[2] & 6) + { + if (args[2] & 8) + { + if (args[2] & 1) + { // linear + dpos.X = CurrNode.Next.pos.X - CurrNode.pos.X; + dpos.Y = CurrNode.Next.pos.Y - CurrNode.pos.Y; + dpos.Z = CurrNode.Next.pos.Z - CurrNode.pos.Z; + } + else if (Time > 0) + { // spline + dpos = newpos - dpos; + } + else + { + int realarg = args[2]; + args[2] &= ~(2|4|8); + Time += 0.1; + dpos = newpos; + Interpolate (); + Time -= 0.1; + args[2] = realarg; + dpos = newpos - dpos; + newpos -= dpos; + SetXYZ(newpos); + } + if (args[2] & 2) + { // adjust yaw + Angle = VectorAngle(dpos.X, dpos.Y); + } + if (args[2] & 4) + { // adjust pitch; + double dist = dpos.XY.Length(); + Pitch = dist != 0 ? VectorAngle(dist, -dpos.Z) : 0.; + } + } + else + { + if (args[2] & 2) + { // interpolate angle + double angle1 = Normalize180(CurrNode.angle); + double angle2 = angle1 + deltaangle(angle1, CurrNode.Next.angle); + angle = Lerp(angle1, angle2); + } + if (args[2] & 1) + { // linear + if (args[2] & 4) + { // interpolate pitch + Pitch = Lerp(CurrNode.Pitch, CurrNode.Next.Pitch); + } + } + else + { // spline + if (args[2] & 4) + { // interpolate pitch + Pitch = Splerp(PrevNode.Pitch, CurrNode.Pitch, + CurrNode.Next.Pitch, CurrNode.Next.Next.Pitch); + } + } + } + } + + return true; + } + } -class ActorMover : PathFollower native +/* +== ActorMover: Moves any actor along a camera path +== +== Same as PathFollower, except +== args[2], bit 7: make nonsolid +== args[3] = tid of thing to move +== +== also uses: +== tracer = thing to move +*/ + +class ActorMover : PathFollower { + override void BeginPlay() + { + ChangeStatNum(STAT_ACTORMOVER); + } + + override void PostBeginPlay () + { + Super.PostBeginPlay (); + + let iterator = ActorIterator.Create(args[3]); + tracer = iterator.Next (); + + if (tracer == null) + { + A_Log("ActorMover " .. tid .. ": Can't find target " .. args[3] .. "\n"); + } + else + { + special1 = tracer.bNoGravity + (tracer.bNoBlockmap<<1) + (tracer.bSolid<<2) + (tracer.bInvulnerable<<4) + (tracer.bDormant<<8); + } + } + + override bool Interpolate () + { + if (tracer == null) + return true; + + if (Super.Interpolate ()) + { + double savedz = tracer.pos.Z; + tracer.SetZ(pos.Z); + if (!tracer.TryMove (Pos.XY, true)) + { + tracer.SetZ(savedz); + return false; + } + + if (args[2] & 2) + tracer.angle = angle; + if (args[2] & 4) + tracer.Pitch = Pitch; + + return true; + } + return false; + } + + override void Activate (Actor activator) + { + if (tracer == null || bActive) + return; + + Super.Activate (activator); + let tracer = self.tracer; + special1 = tracer.bNoGravity + (tracer.bNoBlockmap<<1) + (tracer.bSolid<<2) + (tracer.bInvulnerable<<4) + (tracer.bDormant<<8); + bNoGravity = true; + if (args[2] & 128) + { + LinkContext ctx; + tracer.UnlinkFromWorld (ctx); + bNoBlockmap = true; + bSolid = false; + tracer.LinkToWorld (ctx); + } + if (tracer.bIsMonster) + { + bInvulnerable = true; + bDormant = true; + } + // Don't let the renderer interpolate between the actor's + // old position and its new position. + Interpolate (); + tracer.ClearInterpolation(); + } + + override void Deactivate (Actor activator) + { + if (bActive) + { + Super.Deactivate (activator); + let tracer = self.tracer; + if (tracer != null) + { + LinkContext ctx; + tracer.UnlinkFromWorld (ctx); + tracer.bNoGravity = (special1 & 1); + tracer.bNoBlockmap = !!(special1 & 2); + tracer.bSolid = !!(special1 & 4); + tracer.bInvulnerable = !!(special1 & 8); + tracer.bDormant = !!(special1 & 16); + tracer.LinkToWorld (ctx); + } + } + } } -class MovingCamera : PathFollower native +/* +== MovingCamera: Moves any actor along a camera path +== +== Same as PathFollower, except +== args[3] = tid of thing to look at (0 if none) +== +== Also uses: +== tracer = thing to look at +*/ + +class MovingCamera : PathFollower { + Actor activator; default { CameraHeight 0; } + + override void PostBeginPlay () + { + Super.PostBeginPlay (); + + Activator = null; + if (args[3] != 0) + { + let iterator = ActorIterator.Create(args[3]); + tracer = iterator.Next (); + if (tracer == null) + { + A_Log("MovingCamera " .. tid .. ": Can't find thing " .. args[3] .. "\n"); + } + } + } + + override bool Interpolate () + { + if (tracer == null) + return Super.Interpolate (); + + if (Super.Interpolate ()) + { + angle = AngleTo(tracer, true); + + if (args[2] & 4) + { // Also aim camera's pitch; + Vector3 diff = Pos - tracer.Pos - (0, 0, tracer.Height / 2); + double dist = diff.XY.Length(); + Pitch = dist != 0 ? VectorAngle(dist, diff.Z) : 0.; + } + + return true; + } + return false; + } + } - - From e75aa08d0a713c598c589a79e7b7ae0eddba055f Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 13 Jan 2017 21:44:34 +0200 Subject: [PATCH 05/10] Implemented format() builtin call --- src/namedef.h | 1 + src/scripting/codegeneration/codegen.cpp | 87 +++++++++++++ src/scripting/codegeneration/codegen.h | 25 +++- src/scripting/vm/vm.h | 1 + src/scripting/vm/vmexec.h | 151 +++++++++++++++++++++++ src/scripting/vm/vmframe.cpp | 4 + src/scripting/vm/vmops.h | 1 + 7 files changed, 267 insertions(+), 3 deletions(-) diff --git a/src/namedef.h b/src/namedef.h index 48f1a1943..33e61a276 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -349,6 +349,7 @@ xx(VisibleStartAngle) xx(VisibleStartPitch) xx(VisibleEndAngle) xx(VisibleEndPitch) +xx(Format) // Various actor names which are used internally xx(MapSpot) diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp index b2f9d40eb..d89ba9ae7 100644 --- a/src/scripting/codegeneration/codegen.cpp +++ b/src/scripting/codegeneration/codegen.cpp @@ -7115,6 +7115,14 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx) } } + // [ZZ] string formatting function + if (MethodName == NAME_Format) + { + FxExpression *x = new FxFormat(ArgList, ScriptPosition); + delete this; + return x->Resolve(ctx); + } + int min, max, special; if (MethodName == NAME_ACS_NamedExecuteWithResult || MethodName == NAME_CallACS) { @@ -8341,6 +8349,85 @@ ExpEmit FxFlopFunctionCall::Emit(VMFunctionBuilder *build) } +//========================================================================== +// +// +// +//========================================================================== + +FxFormat::FxFormat(FArgumentList &args, const FScriptPosition &pos) + : FxExpression(EFX_FlopFunctionCall, pos) +{ + ArgList = std::move(args); +} + +//========================================================================== +// +// +// +//========================================================================== + +FxFormat::~FxFormat() +{ +} + +FxExpression *FxFormat::Resolve(FCompileContext& ctx) +{ + CHECKRESOLVED(); + + for (int i = 0; i < ArgList.Size(); i++) + { + ArgList[i] = ArgList[i]->Resolve(ctx); + if (ArgList[i] == nullptr) + { + delete this; + return nullptr; + } + + // first argument should be a string + if (!i && ArgList[i]->ValueType != TypeString) + { + ScriptPosition.Message(MSG_ERROR, "String was expected for format"); + delete this; + return nullptr; + } + + if (ArgList[i]->ValueType == TypeName || + ArgList[i]->ValueType == TypeSound) + { + FxExpression* x = new FxStringCast(ArgList[i]); + x = x->Resolve(ctx); + if (x == nullptr) + { + delete this; + return nullptr; + } + ArgList[i] = x; + } + } + + ValueType = TypeString; + return this; +} + +//========================================================================== +// +// +//========================================================================== + +ExpEmit FxFormat::Emit(VMFunctionBuilder *build) +{ + ExpEmit to = ExpEmit(build, REGT_STRING); + for (int i = 0; i < ArgList.Size(); i++) + { + EmitParameter(build, ArgList[i], ScriptPosition); + } + + build->Emit(OP_STRFMT, to.RegNum, ArgList.Size(), 0); + return to; +} + + //========================================================================== // // diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h index a0a66a776..29369ebc7 100644 --- a/src/scripting/codegeneration/codegen.h +++ b/src/scripting/codegeneration/codegen.h @@ -255,6 +255,7 @@ enum EFxType EFX_MemberFunctionCall, EFX_ActionSpecialCall, EFX_FlopFunctionCall, + EFX_FormatFunctionCall, EFX_VMFunctionCall, EFX_Sequence, EFX_CompoundStatement, @@ -1532,7 +1533,25 @@ public: //========================================================================== // -// FxFlopFunctionCall +// FxFormatFunctionCall +// +//========================================================================== + +class FxFormat : public FxExpression +{ + FArgumentList ArgList; + +public: + + FxFormat(FArgumentList &args, const FScriptPosition &pos); + ~FxFormat(); + FxExpression *Resolve(FCompileContext&); + ExpEmit Emit(VMFunctionBuilder *build); +}; + +//========================================================================== +// +// FxVectorBuiltin // //========================================================================== @@ -1551,7 +1570,7 @@ public: //========================================================================== // -// FxFlopFunctionCall +// FxGetClass // //========================================================================== @@ -1569,7 +1588,7 @@ public: //========================================================================== // -// FxFlopFunctionCall +// FxGetDefaultByType // //========================================================================== diff --git a/src/scripting/vm/vm.h b/src/scripting/vm/vm.h index 1035c2f8e..2a76f8e2e 100644 --- a/src/scripting/vm/vm.h +++ b/src/scripting/vm/vm.h @@ -199,6 +199,7 @@ enum EVMAbortException X_ARRAY_OUT_OF_BOUNDS, X_DIVISION_BY_ZERO, X_BAD_SELF, + X_FORMAT_ERROR }; class CVMAbortException : public CDoomError diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index 1d685c69c..506cb3b4e 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -862,6 +862,157 @@ begin: } NEXTOP; + OP(STRFMT) : + { + ASSERTS(a); + assert(B <= f->NumParam); + int countparam = B; + assert(countparam >= 1); + VMValue* args = reg.param + f->NumParam - B; + assert(args[0].Type == REGT_STRING); + FString fmtstring = args[0].s().GetChars(); + + // note: we don't need a real printf format parser. + // enough to simply find the subtitution tokens and feed them to the real printf after checking types. + // https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification + FString output; + bool in_fmt = false; + FString fmt_current; + int argnum = 1; + int argauto = 1; + // % = starts + // [0-9], -, +, \s, 0, #, . continue + // %, s, d, i, u, fF, eE, gG, xX, o, c, p, aA terminate + // various type flags are not supported. not like stuff like 'hh' modifier is to be used in the VM. + // the only combination that is parsed locally is %n$... + bool haveargnums = false; + for (int i = 0; i < fmtstring.Len(); i++) + { + char c = fmtstring[i]; + if (in_fmt) + { + if ((c >= '0' && c <= '9') || + c == '-' || c == '+' || (c == ' ' && fmt_current[fmt_current.Len() - 1] != ' ') || c == '#' || c == '.') + { + fmt_current += c; + } + else if (c == '$') // %number$format + { + if (!haveargnums && argauto > 1) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + FString argnumstr = fmt_current.Mid(1); + if (!argnumstr.IsInt()) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for argument number, got '%s'.", argnumstr.GetChars()); + argnum = argnumstr.ToLong(); + if (argnum < 1 || argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum, countparam); + fmt_current = "%"; + haveargnums = true; + } + else + { + fmt_current += c; + + switch (c) + { + // string and char formats + case 's': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not a string + if (argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (args[argnum].Type != REGT_STRING) ThrowAbortException(X_FORMAT_ERROR, "Expected a string for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), args[argnum].s().GetChars()); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // int formats + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not an int + if (argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (args[argnum].Type != REGT_INT && + args[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), args[argnum].ToInt()); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // double formats + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + case 'a': + case 'A': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not a float + if (argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (args[argnum].Type != REGT_INT && + args[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), args[argnum].ToDouble()); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + default: + // invalid character + output += fmt_current; + in_fmt = false; + break; + } + } + } + else + { + if (c == '%') + { + if (i + 1 < fmtstring.Len() && fmtstring[i + 1] == '%') + { + output += '%'; + i++; + } + else + { + in_fmt = true; + fmt_current = "%"; + } + } + else + { + output += c; + } + } + } + + // finalize parameters + for (b = B; b != 0; --b) + reg.param[--f->NumParam].~VMValue(); + + reg.s[a] = output; + } + NEXTOP; + OP(SLL_RR): ASSERTD(a); ASSERTD(B); ASSERTD(C); reg.d[a] = reg.d[B] << reg.d[C]; diff --git a/src/scripting/vm/vmframe.cpp b/src/scripting/vm/vmframe.cpp index 09192cbfe..3b97eb614 100644 --- a/src/scripting/vm/vmframe.cpp +++ b/src/scripting/vm/vmframe.cpp @@ -549,6 +549,10 @@ CVMAbortException::CVMAbortException(EVMAbortException reason, const char *morei AppendMessage("invalid self pointer."); break; + case X_FORMAT_ERROR: + AppendMessage("string format failed."); + break; + default: { size_t len = strlen(m_Message); diff --git a/src/scripting/vm/vmops.h b/src/scripting/vm/vmops.h index 889706726..2d76adb7e 100644 --- a/src/scripting/vm/vmops.h +++ b/src/scripting/vm/vmops.h @@ -120,6 +120,7 @@ xx(BOUND_R, bound, RIRI, NOP, 0, 0), // if rA >= rB, throw exception xx(CONCAT, concat, RSRSRS, NOP, 0, 0), // sA = sB..sC xx(LENS, lens, RIRS, NOP, 0, 0), // dA = sB.Length xx(CMPS, cmps, I8RXRX, NOP, 0, 0), // if ((skB op skC) != (A & 1)) then pc++ +xx(STRFMT, strfmt, RIRIRI, NOP, 0, 0), // Integer math. xx(SLL_RR, sll, RIRIRI, NOP, 0, 0), // dA = dkB << diC From fcc5f4b77b024b63b24fd64351d43abe64dcfab8 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 13 Jan 2017 22:29:03 +0200 Subject: [PATCH 06/10] Fixed EFX_ enum for FxFormat --- src/scripting/codegeneration/codegen.cpp | 2 +- src/scripting/codegeneration/codegen.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp index d89ba9ae7..756f2083f 100644 --- a/src/scripting/codegeneration/codegen.cpp +++ b/src/scripting/codegeneration/codegen.cpp @@ -8356,7 +8356,7 @@ ExpEmit FxFlopFunctionCall::Emit(VMFunctionBuilder *build) //========================================================================== FxFormat::FxFormat(FArgumentList &args, const FScriptPosition &pos) - : FxExpression(EFX_FlopFunctionCall, pos) + : FxExpression(EFX_Format, pos) { ArgList = std::move(args); } diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h index 29369ebc7..97fdf198e 100644 --- a/src/scripting/codegeneration/codegen.h +++ b/src/scripting/codegeneration/codegen.h @@ -255,7 +255,7 @@ enum EFxType EFX_MemberFunctionCall, EFX_ActionSpecialCall, EFX_FlopFunctionCall, - EFX_FormatFunctionCall, + EFX_Format, EFX_VMFunctionCall, EFX_Sequence, EFX_CompoundStatement, From ee2ecf145017380a4593b745a22e720957a442f4 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 13 Jan 2017 22:40:07 +0200 Subject: [PATCH 07/10] Added %c (from int) and %p (from pointer) support to format() --- src/scripting/vm/vmexec.h | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index 506cb3b4e..43c1fec36 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -913,7 +913,7 @@ begin: switch (c) { - // string and char formats + // string case 's': { if (argnum < 0 && haveargnums) @@ -929,13 +929,30 @@ begin: break; } - // int formats + // pointer + case 'p': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not a string + if (argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (args[argnum].Type != REGT_POINTER) ThrowAbortException(X_FORMAT_ERROR, "Expected a pointer for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), args[argnum].a); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // int formats (including char) case 'd': case 'i': case 'u': case 'x': case 'X': case 'o': + case 'c': { if (argnum < 0 && haveargnums) ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); From a8beb51ca3153c8409db4e2e182bbf30c7dde601 Mon Sep 17 00:00:00 2001 From: ZZYZX Date: Fri, 13 Jan 2017 23:49:36 +0200 Subject: [PATCH 08/10] Changed opcode implementation to native function implementation --- src/namedef.h | 1 + src/scripting/codegeneration/codegen.cpp | 206 ++++++++++++++++++++++- src/scripting/codegeneration/codegen.h | 4 +- src/scripting/vm/vmexec.h | 168 ------------------ src/scripting/vm/vmops.h | 1 - 5 files changed, 204 insertions(+), 176 deletions(-) diff --git a/src/namedef.h b/src/namedef.h index 33e61a276..aa7969193 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -700,6 +700,7 @@ xx(BuiltinFindSingleNameState) xx(BuiltinHandleRuntimeState) xx(BuiltinGetDefault) xx(BuiltinClassCast) +xx(BuiltinFormat) xx(Damage) // basic type names diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp index 756f2083f..5e2878cac 100644 --- a/src/scripting/codegeneration/codegen.cpp +++ b/src/scripting/codegeneration/codegen.cpp @@ -8358,6 +8358,7 @@ ExpEmit FxFlopFunctionCall::Emit(VMFunctionBuilder *build) FxFormat::FxFormat(FArgumentList &args, const FScriptPosition &pos) : FxExpression(EFX_Format, pos) { + EmitTail = false; ArgList = std::move(args); } @@ -8371,6 +8372,24 @@ FxFormat::~FxFormat() { } +//========================================================================== +// +// +// +//========================================================================== + +PPrototype *FxFormat::ReturnProto() +{ + EmitTail = true; + return FxExpression::ReturnProto(); +} + +//========================================================================== +// +// +// +//========================================================================== + FxExpression *FxFormat::Resolve(FCompileContext& ctx) { CHECKRESOLVED(); @@ -8415,16 +8434,191 @@ FxExpression *FxFormat::Resolve(FCompileContext& ctx) // //========================================================================== -ExpEmit FxFormat::Emit(VMFunctionBuilder *build) +static int BuiltinFormat(VMValue *args, TArray &defaultparam, int numparam, VMReturn *ret, int numret) { - ExpEmit to = ExpEmit(build, REGT_STRING); - for (int i = 0; i < ArgList.Size(); i++) + assert(args[0].Type == REGT_STRING); + FString fmtstring = args[0].s().GetChars(); + + // note: we don't need a real printf format parser. + // enough to simply find the subtitution tokens and feed them to the real printf after checking types. + // https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification + FString output; + bool in_fmt = false; + FString fmt_current; + int argnum = 1; + int argauto = 1; + // % = starts + // [0-9], -, +, \s, 0, #, . continue + // %, s, d, i, u, fF, eE, gG, xX, o, c, p, aA terminate + // various type flags are not supported. not like stuff like 'hh' modifier is to be used in the VM. + // the only combination that is parsed locally is %n$... + bool haveargnums = false; + for (int i = 0; i < fmtstring.Len(); i++) { - EmitParameter(build, ArgList[i], ScriptPosition); + char c = fmtstring[i]; + if (in_fmt) + { + if ((c >= '0' && c <= '9') || + c == '-' || c == '+' || (c == ' ' && fmt_current[fmt_current.Len() - 1] != ' ') || c == '#' || c == '.') + { + fmt_current += c; + } + else if (c == '$') // %number$format + { + if (!haveargnums && argauto > 1) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + FString argnumstr = fmt_current.Mid(1); + if (!argnumstr.IsInt()) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for argument number, got '%s'.", argnumstr.GetChars()); + argnum = argnumstr.ToLong(); + if (argnum < 1 || argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum, numparam); + fmt_current = "%"; + haveargnums = true; + } + else + { + fmt_current += c; + + switch (c) + { + // string + case 's': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not a string + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (args[argnum].Type != REGT_STRING) ThrowAbortException(X_FORMAT_ERROR, "Expected a string for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), args[argnum].s().GetChars()); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // pointer + case 'p': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not a string + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (args[argnum].Type != REGT_POINTER) ThrowAbortException(X_FORMAT_ERROR, "Expected a pointer for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), args[argnum].a); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // int formats (including char) + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + case 'c': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not an int + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (args[argnum].Type != REGT_INT && + args[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), args[argnum].ToInt()); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + // double formats + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + case 'a': + case 'A': + { + if (argnum < 0 && haveargnums) + ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); + in_fmt = false; + // fail if something was found, but it's not a float + if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); + if (args[argnum].Type != REGT_INT && + args[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); + // append + output.AppendFormat(fmt_current.GetChars(), args[argnum].ToDouble()); + if (!haveargnums) argnum = ++argauto; + else argnum = -1; + break; + } + + default: + // invalid character + output += fmt_current; + in_fmt = false; + break; + } + } + } + else + { + if (c == '%') + { + if (i + 1 < fmtstring.Len() && fmtstring[i + 1] == '%') + { + output += '%'; + i++; + } + else + { + in_fmt = true; + fmt_current = "%"; + } + } + else + { + output += c; + } + } } - build->Emit(OP_STRFMT, to.RegNum, ArgList.Size(), 0); - return to; + ACTION_RETURN_STRING(output); +} + +ExpEmit FxFormat::Emit(VMFunctionBuilder *build) +{ + // Call DecoRandom to generate a random number. + VMFunction *callfunc; + PSymbol *sym = FindBuiltinFunction(NAME_BuiltinFormat, BuiltinFormat); + + assert(sym->IsKindOf(RUNTIME_CLASS(PSymbolVMFunction))); + assert(((PSymbolVMFunction *)sym)->Function != nullptr); + callfunc = ((PSymbolVMFunction *)sym)->Function; + + if (build->FramePointer.Fixed) EmitTail = false; // do not tail call if the stack is in use + int opcode = (EmitTail ? OP_TAIL_K : OP_CALL_K); + + for (int i = 0; i < ArgList.Size(); i++) + EmitParameter(build, ArgList[i], ScriptPosition); + build->Emit(opcode, build->GetConstantAddress(callfunc, ATAG_OBJECT), ArgList.Size(), 1); + + if (EmitTail) + { + ExpEmit call; + call.Final = true; + return call; + } + + ExpEmit out(build, REGT_STRING); + build->Emit(OP_RESULT, 0, REGT_STRING, out.RegNum); + return out; } diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h index 97fdf198e..ed4c2f97c 100644 --- a/src/scripting/codegeneration/codegen.h +++ b/src/scripting/codegeneration/codegen.h @@ -1533,19 +1533,21 @@ public: //========================================================================== // -// FxFormatFunctionCall +// FxFormat // //========================================================================== class FxFormat : public FxExpression { FArgumentList ArgList; + bool EmitTail; public: FxFormat(FArgumentList &args, const FScriptPosition &pos); ~FxFormat(); FxExpression *Resolve(FCompileContext&); + PPrototype *ReturnProto(); ExpEmit Emit(VMFunctionBuilder *build); }; diff --git a/src/scripting/vm/vmexec.h b/src/scripting/vm/vmexec.h index 43c1fec36..1d685c69c 100644 --- a/src/scripting/vm/vmexec.h +++ b/src/scripting/vm/vmexec.h @@ -862,174 +862,6 @@ begin: } NEXTOP; - OP(STRFMT) : - { - ASSERTS(a); - assert(B <= f->NumParam); - int countparam = B; - assert(countparam >= 1); - VMValue* args = reg.param + f->NumParam - B; - assert(args[0].Type == REGT_STRING); - FString fmtstring = args[0].s().GetChars(); - - // note: we don't need a real printf format parser. - // enough to simply find the subtitution tokens and feed them to the real printf after checking types. - // https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification - FString output; - bool in_fmt = false; - FString fmt_current; - int argnum = 1; - int argauto = 1; - // % = starts - // [0-9], -, +, \s, 0, #, . continue - // %, s, d, i, u, fF, eE, gG, xX, o, c, p, aA terminate - // various type flags are not supported. not like stuff like 'hh' modifier is to be used in the VM. - // the only combination that is parsed locally is %n$... - bool haveargnums = false; - for (int i = 0; i < fmtstring.Len(); i++) - { - char c = fmtstring[i]; - if (in_fmt) - { - if ((c >= '0' && c <= '9') || - c == '-' || c == '+' || (c == ' ' && fmt_current[fmt_current.Len() - 1] != ' ') || c == '#' || c == '.') - { - fmt_current += c; - } - else if (c == '$') // %number$format - { - if (!haveargnums && argauto > 1) - ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); - FString argnumstr = fmt_current.Mid(1); - if (!argnumstr.IsInt()) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for argument number, got '%s'.", argnumstr.GetChars()); - argnum = argnumstr.ToLong(); - if (argnum < 1 || argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum, countparam); - fmt_current = "%"; - haveargnums = true; - } - else - { - fmt_current += c; - - switch (c) - { - // string - case 's': - { - if (argnum < 0 && haveargnums) - ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); - in_fmt = false; - // fail if something was found, but it's not a string - if (argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); - if (args[argnum].Type != REGT_STRING) ThrowAbortException(X_FORMAT_ERROR, "Expected a string for format %s.", fmt_current.GetChars()); - // append - output.AppendFormat(fmt_current.GetChars(), args[argnum].s().GetChars()); - if (!haveargnums) argnum = ++argauto; - else argnum = -1; - break; - } - - // pointer - case 'p': - { - if (argnum < 0 && haveargnums) - ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); - in_fmt = false; - // fail if something was found, but it's not a string - if (argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); - if (args[argnum].Type != REGT_POINTER) ThrowAbortException(X_FORMAT_ERROR, "Expected a pointer for format %s.", fmt_current.GetChars()); - // append - output.AppendFormat(fmt_current.GetChars(), args[argnum].a); - if (!haveargnums) argnum = ++argauto; - else argnum = -1; - break; - } - - // int formats (including char) - case 'd': - case 'i': - case 'u': - case 'x': - case 'X': - case 'o': - case 'c': - { - if (argnum < 0 && haveargnums) - ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); - in_fmt = false; - // fail if something was found, but it's not an int - if (argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); - if (args[argnum].Type != REGT_INT && - args[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); - // append - output.AppendFormat(fmt_current.GetChars(), args[argnum].ToInt()); - if (!haveargnums) argnum = ++argauto; - else argnum = -1; - break; - } - - // double formats - case 'f': - case 'F': - case 'e': - case 'E': - case 'g': - case 'G': - case 'a': - case 'A': - { - if (argnum < 0 && haveargnums) - ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments."); - in_fmt = false; - // fail if something was found, but it's not a float - if (argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format."); - if (args[argnum].Type != REGT_INT && - args[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars()); - // append - output.AppendFormat(fmt_current.GetChars(), args[argnum].ToDouble()); - if (!haveargnums) argnum = ++argauto; - else argnum = -1; - break; - } - - default: - // invalid character - output += fmt_current; - in_fmt = false; - break; - } - } - } - else - { - if (c == '%') - { - if (i + 1 < fmtstring.Len() && fmtstring[i + 1] == '%') - { - output += '%'; - i++; - } - else - { - in_fmt = true; - fmt_current = "%"; - } - } - else - { - output += c; - } - } - } - - // finalize parameters - for (b = B; b != 0; --b) - reg.param[--f->NumParam].~VMValue(); - - reg.s[a] = output; - } - NEXTOP; - OP(SLL_RR): ASSERTD(a); ASSERTD(B); ASSERTD(C); reg.d[a] = reg.d[B] << reg.d[C]; diff --git a/src/scripting/vm/vmops.h b/src/scripting/vm/vmops.h index 2d76adb7e..889706726 100644 --- a/src/scripting/vm/vmops.h +++ b/src/scripting/vm/vmops.h @@ -120,7 +120,6 @@ xx(BOUND_R, bound, RIRI, NOP, 0, 0), // if rA >= rB, throw exception xx(CONCAT, concat, RSRSRS, NOP, 0, 0), // sA = sB..sC xx(LENS, lens, RIRS, NOP, 0, 0), // dA = sB.Length xx(CMPS, cmps, I8RXRX, NOP, 0, 0), // if ((skB op skC) != (A & 1)) then pc++ -xx(STRFMT, strfmt, RIRIRI, NOP, 0, 0), // Integer math. xx(SLL_RR, sll, RIRIRI, NOP, 0, 0), // dA = dkB << diC From f759b6757aa9ab92e9e50e9e4adca6a2569d5756 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 13 Jan 2017 23:17:04 +0100 Subject: [PATCH 09/10] - scriptified the teleport fog. --- src/g_shared/a_morph.cpp | 8 ++++---- src/g_shared/a_sharedglobal.h | 7 ------- src/gl/dynlights/a_dynlight.cpp | 8 ++++---- src/p_teleport.cpp | 22 ---------------------- wadsrc/static/zscript/shared/teleport.txt | 23 ++++++++++++++++++++++- 5 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/g_shared/a_morph.cpp b/src/g_shared/a_morph.cpp index 0ca8e3945..bb03ff489 100644 --- a/src/g_shared/a_morph.cpp +++ b/src/g_shared/a_morph.cpp @@ -116,7 +116,7 @@ bool P_MorphPlayer (player_t *activator, player_t *p, PClassPlayerPawn *spawntyp morphed->flags |= actor->flags & (MF_SHADOW|MF_NOGRAVITY); morphed->flags2 |= actor->flags2 & MF2_FLY; morphed->flags3 |= actor->flags3 & MF3_GHOST; - AActor *eflash = Spawn(((enter_flash) ? enter_flash : RUNTIME_CLASS(ATeleportFog)), actor->PosPlusZ(TELEFOGHEIGHT), ALLOW_REPLACE); + AActor *eflash = Spawn(((enter_flash) ? enter_flash : PClass::FindActor("TeleportFog")), actor->PosPlusZ(TELEFOGHEIGHT), ALLOW_REPLACE); actor->player = nullptr; actor->alternative = morphed; actor->flags &= ~(MF_SOLID|MF_SHOOTABLE); @@ -128,7 +128,7 @@ bool P_MorphPlayer (player_t *activator, player_t *p, PClassPlayerPawn *spawntyp p->MorphedPlayerClass = spawntype; p->MorphStyle = style; - p->MorphExitFlash = (exit_flash) ? exit_flash : RUNTIME_CLASS(ATeleportFog); + p->MorphExitFlash = (exit_flash) ? exit_flash : PClass::FindActor("TeleportFog"); p->health = morphed->health; p->mo = morphed; p->Vel.X = p->Vel.Y = 0; @@ -416,7 +416,7 @@ bool P_MorphMonster (AActor *actor, PClassActor *spawntype, int duration, int st morphed->UnmorphTime = level.time + ((duration) ? duration : MORPHTICS) + pr_morphmonst(); morphed->MorphStyle = style; - morphed->MorphExitFlash = (exit_flash) ? exit_flash : RUNTIME_CLASS(ATeleportFog); + morphed->MorphExitFlash = (exit_flash) ? exit_flash : PClass::FindActor("TeleportFog"); morphed->FlagsSave = actor->flags & ~MF_JUSTHIT; morphed->special = actor->special; memcpy (morphed->args, actor->args, sizeof(actor->args)); @@ -434,7 +434,7 @@ bool P_MorphMonster (AActor *actor, PClassActor *spawntype, int duration, int st actor->flags &= ~(MF_SOLID|MF_SHOOTABLE); actor->flags |= MF_UNMORPHED; actor->renderflags |= RF_INVISIBLE; - AActor *eflash = Spawn(((enter_flash) ? enter_flash : RUNTIME_CLASS(ATeleportFog)), actor->PosPlusZ(TELEFOGHEIGHT), ALLOW_REPLACE); + AActor *eflash = Spawn(((enter_flash) ? enter_flash : PClass::FindActor("TeleportFog")), actor->PosPlusZ(TELEFOGHEIGHT), ALLOW_REPLACE); if (eflash) eflash->target = morphed; return true; diff --git a/src/g_shared/a_sharedglobal.h b/src/g_shared/a_sharedglobal.h index 93e0c7e0e..bee819dd0 100644 --- a/src/g_shared/a_sharedglobal.h +++ b/src/g_shared/a_sharedglobal.h @@ -76,13 +76,6 @@ private: DImpactDecal(); }; -class ATeleportFog : public AActor -{ - DECLARE_CLASS (ATeleportFog, AActor) -public: - void PostBeginPlay (); -}; - class ASkyViewpoint : public AActor { DECLARE_CLASS (ASkyViewpoint, AActor) diff --git a/src/gl/dynlights/a_dynlight.cpp b/src/gl/dynlights/a_dynlight.cpp index 98b5d1315..a844d4880 100644 --- a/src/gl/dynlights/a_dynlight.cpp +++ b/src/gl/dynlights/a_dynlight.cpp @@ -109,10 +109,10 @@ DEFINE_CLASS_PROPERTY(type, S, DynamicLight) // which is controlled by flags // //========================================================================== -IMPLEMENT_CLASS (ADynamicLight, false, false) -IMPLEMENT_CLASS (AVavoomLight, false, false) -IMPLEMENT_CLASS (AVavoomLightWhite, false, false) -IMPLEMENT_CLASS (AVavoomLightColor, false, false) +IMPLEMENT_CLASS(ADynamicLight, false, false) +IMPLEMENT_CLASS(AVavoomLight, false, false) +IMPLEMENT_CLASS(AVavoomLightWhite, false, false) +IMPLEMENT_CLASS(AVavoomLightColor, false, false) void AVavoomLight::BeginPlay () { diff --git a/src/p_teleport.cpp b/src/p_teleport.cpp index 5eb63e4ce..b34b4de1d 100644 --- a/src/p_teleport.cpp +++ b/src/p_teleport.cpp @@ -48,28 +48,6 @@ extern void P_CalcHeight (player_t *player); CVAR (Bool, telezoom, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); -IMPLEMENT_CLASS(ATeleportFog, false, false) - -void ATeleportFog::PostBeginPlay () -{ - Super::PostBeginPlay (); - S_Sound (this, CHAN_BODY, "misc/teleport", 1, ATTN_NORM); - switch (gameinfo.gametype) - { - case GAME_Hexen: - case GAME_Heretic: - SetState(FindState(NAME_Raven)); - break; - - case GAME_Strife: - SetState(FindState(NAME_Strife)); - break; - - default: - break; - } -} - //========================================================================== // // P_SpawnTeleportFog diff --git a/wadsrc/static/zscript/shared/teleport.txt b/wadsrc/static/zscript/shared/teleport.txt index 55308415f..acf4a6d75 100644 --- a/wadsrc/static/zscript/shared/teleport.txt +++ b/wadsrc/static/zscript/shared/teleport.txt @@ -1,5 +1,5 @@ -class TeleportFog : Actor native +class TeleportFog : Actor { default { @@ -22,6 +22,27 @@ class TeleportFog : Actor native TFOG ABCDEFEDCB 6 Bright; Stop; } + + override void PostBeginPlay () + { + Super.PostBeginPlay (); + A_PlaySound ("misc/teleport", CHAN_BODY); + switch (gametype()) + { + case GAME_Hexen: + case GAME_Heretic: + SetStateLabel("Raven"); + break; + + case GAME_Strife: + SetStateLabel("Strife"); + break; + + default: + break; + } + } + } From 4d68f066a0df4cda38765d828777d9b328b8dafd Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 13 Jan 2017 23:29:52 +0100 Subject: [PATCH 10/10] - fixed: sector.gravity was not serialized. --- src/p_saveg.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 227fc91fd..8dc46e922 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -275,6 +275,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, sector_t &p, sector_t ("linked_floor", p.e->Linked.Floor.Sectors) ("linked_ceiling", p.e->Linked.Ceiling.Sectors) ("colormap", p.ColorMap, def->ColorMap) + ("gravity", p.gravity, def->gravity) .Terrain("floorterrain", p.terrainnum[0], &def->terrainnum[0]) .Terrain("ceilingterrain", p.terrainnum[1], &def->terrainnum[1]) ("scrolls", scroll, nul)