This is a actor class whose main feature is the ability to carry other actors on top of itself while it moves around.
Possibilities include:
- Old-fashioned platforming
- Enemies that ride moving "cars" while attacking you.
- Floors and ceilings opening up.
- Any scenario at all where you can imagine a horizontally moving piece of "geometry" that you can stand on and be carried by.
- Pushable crates that can carry other crates (since v1.1.0)
Movement is based on GZDoom's PathFollower. That is, it moves by using map placed interpolation points. But it can also be moved/rotated via ACS since it has dedicated ACS utility functions.
A short demo map (MAP01) is included.
All the models in that map were created with UDB, but a platform can be a sprite, too.
Github repo:
https://github.com/FishyClock/3Dplatform
Latest release:
Github: https://github.com/FishyClock/3Dplatfor ... s/tag/v2.5 (The PK3 is inside the ZIP)
Dropbox: https://www.dropbox.com/scl/fi/tt6sy0y5 ... plmhu&dl=1
Lite PK3 version:
Dropbox: https://www.dropbox.com/scl/fi/tihb69jb ... gr2bh&dl=1
All github releases:
https://github.com/FishyClock/3Dplatform/releases
Feel free to use this for your own projects as long as you mention me in the credits.
This is licensed under GPL.
I highly recommend you change the "Fishy" prefix to avoid potential conflicts with other mods that might use this library.
Spoiler: More details
Spoiler: Platform mapthing arguments, user variables, actor flags & properties
The FishyPlatform class has its own custom arguments.
Platforms can be grouped together so that when one is activated the others will move with it and rotate around it as the mover rotates. This main platform will be referred to as the "origin" or "group origin".
"Group move" affects movement imposed by the group origin.
Flag 32768 is intended for very large, tightly packed platforms that have a lot of passengers.
Normally when a platform moves, all of its passengers will be moved at once without colliding with each other.
Flag 32768 changes this subtly by making sure all passengers from all platforms in the group won't collide with each other.
(This is not always desirable however. That's why it's optional.)
Additional options can be found in the "Custom" tab. (Assuming you're using UDB.)
All of these are user variables which means they can be changed with SetUserVariable or checked with GetUserVariable
Spoiler: Platform user variables and what they do
bool user_scalesize - If true, the platform's radius and height will be scaled by the "Scale" values under the "Action / Tag / Misc." tab.Spoiler: Generic sounds
Each platform can play generic "start", "stop", "move", and "blocked" sounds.
string (text) user_snd_start - plays when movement starts.
string (text) user_snd_stop - plays when it's finished moving.
string (text) user_snd_blocked - plays when movement is blocked.
string (text) user_snd_move - plays looping sound when moving.
bool user_snd_movestopsstart - If true, "move" interrupts "start" when it plays. Otherwise they coexist.
int user_snd_delaytomove - tics to wait after "start" has played before playing "move" sound.
If "user_snd_delaytomove" <= 0, "move" will wait until "start" is done.
"user_snd_movestopsstart" is only relevant if "user_snd_delaytomove" is > 0
If you want "start" and "move" to play at the "same time", "user_snd_delaytomove" must be at least 1
See the "Generic sound test" (SNDTEST) map to see this feature in actionSpoiler: Perpetual rotation speeds
To complement the pivot feature, platforms can have perpetual rotation speeds defined (turnspeeds).
They come in three categories for angle, pitch, and roll. "onpath", "withvel" and "idle".
double (decimal) user_turnspeed_angle_onpath
double (decimal) user_turnspeed_angle_withvel
double (decimal) user_turnspeed_angle_idle
double (decimal) user_turnspeed_pitch_onpath
double (decimal) user_turnspeed_pitch_withvel
double (decimal) user_turnspeed_pitch_idle
double (decimal) user_turnspeed_roll_onpath
double (decimal) user_turnspeed_roll_withvel
double (decimal) user_turnspeed_roll_idle
When a platform is actively following its path (and not waiting), the "onpath" turnspeeds will take effect.
Else, when a platform is moving because of its velocity, the "withvel" turnspeeds will take effect.
Else, when it's not moving whatsoever, the "idle" turnspeeds will take effect.
Note about path following:
If using one of the options "Use point angle/pitch/roll" then the associated turnspeed is nullified!
(Eg. setting "Use point angle" will override/nullify "user_turnspeed_angle_onpath".)
Also, when using ACS commands to move platforms around, if you declare a rotation to occur (eg. with FishyPlat_Rotate)
then that rotation will nullify/override the associated turn speed. In other words. No given rotation = use "onpath" turnspeed.
For example. To have a platform whose angle turns clockwise when idle/waiting but not when moving,
set "user_turnspeed_angle_idle" to -8. (Positive values turn counter-clockwise.)
To have a platform that only turns when following its path but not when idle/waiting,
set "user_turnspeed_angle_onpath" to -8.
To have a pushable crate-like platform that only turns when pushed (it's a velocity move)
set "user_turnspeed_angle_withvel" to -8.
Note about groups:
These turnspeeds have no effect on platforms that are part of a group and are not the "main mover/origin".
If every group member starts as inactive then there is no "origin".
In such a ambiguous situation, the first platform with a non-zero "idle" turnspeed
becomes the new "origin" simply because it starts turning at that point, which counts as movement for the group logic.
So in short. The priority is "onpath" (travelling) > "withvel" (velocity move) > "idle" (not moving)Spoiler: Additional ones from the generic model subclass
double (decimal) user_set_radius - sets the radius for this platform. If negative, use its default radius of 16.
double (decimal) user_set_height - sets the height for this platform. If negative, use its default height of 16.
bool user_modelsetssize - If true and a model is provided, this will override user_set_height and user_set_radius.
Currently only works with the OBJ model format.
The rest are parameters that will be fed into a A_ChangeModel call - that's how this actor sets its model.
At the moment, user variables cannot accept named constants or aliases of any kind.
So here are the numeric values to each of the flags you can give to "user_cm_flags"
CMDL_WEAPONTOPLAYER - 1
CMDL_HIDEMODEL - 2 (1 << 1 == 2)
CMDL_USESURFACESKIN - 4 (1 << 2 == 4)
string (text) user_cm_modeldef
int user_cm_modelindex
string (text) user_cm_modelpath
string (text) user_cm_model
int user_cm_skinindex
string (text) user_cm_skinpath
string (text) user_cm_skin
int user_cm_flags - Tip: if the flag (bit) value includes 2 (or just set it to 2) the model will be invisible
int user_cm_generatorindex
int user_cm_animationindex
string (text) user_cm_animationpath
string (text) user_cm_animation
While you can use this however you want, the usual intended practice is you would use UDB to export level geometry, then set "user_cm_modelpath" to the path (if any) where your model is and set "user_cm_model" to the model file, including its extension. (In UDB's case the extension would be .obj)
Alternatively you can just set "user_cm_model" to "modelpath/modelfile.extension" and it will work.Spoiler: Platform actor flags and properties
- Actor flag: +FishyPlatform.CARRIABLE - Let's this platform be carried (like a passenger) by other platforms.
- Actor flag: +FishyPlatform.USEACTORTICK - If this platform is not in a group, call Actor.Tick() in our Tick() override to handle world interaction. (For things like scrolling floors.)
- Actor flag: +FishyPlatform.NOPASSORBIT - Passengers are unaffected by platform angle/pitch/roll changes.
- Actor flag: +FishyPlatform.NOCORPSEGIB - Don't gib corpses when touching them.
- Property: FishyPlatform.AirFriction - for +NOGRAVITY & +PUSHABLE platforms. (So they don't fly until hitting a wall.)
If the platform does not have NOGRAVITY set then the pre-existing friction property and sector friction take effect instead.
You can dynamically change these flags with SetActorFlag like any other actor flag. And you don't need to use the classname as a prefix.
For example:is perfectly valid. For each platform whose TID is 5, the USEACTORTICK flag will be set to true.Code: Select all
SetActorFlag(5, "USEACTORTICK", true);
Likewise you can use CheckFlag on them as well.
In contrast to the "AirFriction" property - if you want to dynamically change or check that then you have to use FishyPlat_SetAirFriction and FishyPlat_GetAirFriction
Spoiler: Platform interpolation point mapthing arguments & user variables
What's also included is a platform specific interpolation point class. (Though the old interpolation point class is still usable).
Features include: travel/hold time can be in seconds or tics. Travel/hold time can have independent time units.
Setting travel time to a negative value is interpreted as speed in map units per tic. (This also works on the old interpolation point class.)
If you want to set pitch, you have to do it from the "Properties" tab, just like angle and roll.
(Assuming you're using Ultimate Doom Builder as your map editor.)
Speaking of, you can have platforms that interpolate their roll, just like angle and pitch.
The platform interpolation point arguments are thus
The platform interpolation point has its own user variables...
Spoiler: What those user variables do
bool user_nopositionchange - If true, only use point to adjust angle/pitch/roll, NOT as travel destination
bool user_ignoreaxis_x - If true, platform will ignore point's X pos
bool user_ignoreaxis_y - If true, platform will ignore point's Y pos
bool user_ignoreaxis_z - If true, platform will ignore point's Z pos
bool user_ignorepivot - If true and this point is the destination, platform will not rotate around its pivot
bool user_undopivotadjustment - If true, when this point is the platform's destination, reset the platform's offseted travel pos (if any)
Path following and pivot behavior can coexist.
Pivot behavior: Platform position changes by rotating
around its pivot due to angle/pitch/roll adjustments.
This affects the travel path, and the adjusted
position persists even if rotation stops.
NOTE: Variables ignoring position changes (e.g.
user_ignoreaxis_x) only affect path following, NOT
pivot-related position changes.
To ensure the platform moves to this point's exact
position ignoring prior pivot adjustments (assuming
no angle changes, or "user_ignorepivot" was true), set
"user_undopivotadjustment" to true.Spoiler: Pivot point mapthing arguments & ACS functionsUnlike PathFollower and its subclasses, platforms can intelligently use portals.
A "pivot" is a point to rotate around for the platform. Rotations occur when the platform's
angle(yaw), pitch, and/or roll changes.
The pivot can be defined by placing a specialized mapspot ("Platform pivot" / FishyPlatformPivot).
Or it can be defined/removed via ACS. See FishyPlat_SetPivot and FishyPlat_RemovePivot
The mapspot has a few mapthing arguments
The pivot can optionally be attached to the platform (the pivot distance is always the same).
The mapspot can also be set to need activation before it sets any pivot data. Otherwise it will set the data as soon as the map starts.
Please note that the pivot data is set internally and is not dependent on any spot's actual position!
If the "Platform(s)" argument is 0 and the activator is a platform then the spot will set the activator's pivot data.
Note about interpolation specials:
Unlike PathFollowers, when a platform triggers a InterpolationSpecial thing, the platform is considered the activator.
Therefore you can set up a InterpolationSpecial to activate a FishyPlatformPivot who in turn (assuming "Platform(s)" argument is 0) will set that platform's pivot data.
Note about groups:
If a platform is in a group and is not the "main mover/origin", it will ignore any assigned pivot!
See the "Pivot test" (PIVOTS) map to see this feature in action.
Though those are only static line portals and interactive sector portals.
There are several ACS utility functions that can be used with platforms.
Check out: https://github.com/FishyClock/3Dplatfor ... /fplat.acs
There's also a few test maps in addition to the demo map. Feel free to look at those too.
Spoiler: There are 4 virtual functions you can override
Three empty virtual functions for modders to ease up making special actors
work with platforms:
PassengerPreMove - which is always called before attempting to move a passenger.
PassengerPostMove - which is always called after attempting to move a passenger.
And
SpecialBTIActor - which allows intercepting actors detected in the platform's BlockThingIterator searches.
Return "true" to skip processing this actor (mo) and go to the next blockmap result.
Return "false" to do the usual checks (Is it within range, is it carriable, etc).
And the 4th function which is called:
GetCrushDamageSource - which can be used to determine crush damage damagetype as well as who gets blamed for causing the damage.Spoiler: Known issues
Due to how the engine handles actor-to-actor interactions, the platform class can only carry things that have the following actor flags:
+CANPASS - Usually players and monsters
+SPECIAL - Items
An actor with neither flag simply cannot stand on another actor. CANPASS actors can stand on other CANPASS actors. And actors with SPECIAL can stand on actors with ACTLIKEBRIDGE. The platform class has the ACTLIKEBRIDGE and CANPASS flags.
In other words, simple decorations and the like would just fall through. I cannot think of a good way to work around this other than give decorations the +CANPASS flag.
Another thing, the platform cannot carry anything that does have one of the following actor flags:
+FLOORHUGGER
+CEILINGHUGGER
+CANTLEAVEFLOORPIC
This is more deliberate on my part. Because floor/ceiling huggers aren't meant to be in mid-air
and CANTLEAVEFLOORPIC is similar to FLOORHUGGER in that apparently CheckMove() considers it invalid if a move results in the actor's Z coordinate being different from its floorZ. So I decided to exclude CANTLEAVEFLOORPIC actors as well.
While ACTLIKEBRIDGE makes items stand on platforms (and not fall through) and makes monsters be able to walk around on the platforms, this actor flag has the unwanted ability of making certain sector effects not change the platforms position as you would expect. Meaning, a lift cannot push up a platform but will go back down instead, a moving non-lift floor will simply get blocked and not move until the platform moves out of the way.
Same deal for non-crusher ceilings.
This behavior is intentional from the engine side of things. There's a feature request thread asking for the sector effect restrictions to be removed: viewtopic.php?t=76121
Regarding the generic platform introduced in v2.0
Since the parameters for A_ChangeModel are set via user variables it's not possible to use constants for the flags parameter. At the moment, this is a UDB issue. The constants' values can be found here: https://github.com/ZDoom/gzdoom/blob/9e ... #L368-L374
CMDL_WEAPONTOPLAYER = 1
CMDL_HIDEMODEL = 2
CMDL_USESURFACESKIN = 4
Too many passengers on one platform can cause those passengers to fall off more easily, even if the platform is very large. This is more likely if the platform moves and rotates a lot.
Also
Quaternions are used for all rotations in the group logic and pivot logic.
The algorithm for converting a quaternion to euler angles has one
math problem that I'm told is unsolveable. If the resulting pitch is close to
+90/-90 degrees it messes up the resulting angle(yaw) and roll.
This algorithm is used by the "main mover/origin" of a group to set
the euler angles of the other groupmates (not itself).
To see what I mean, assuming every group member faces the same direction
(has the same angle/pitch/roll values)
If the "origin" platform slowly turns 90 or more degrees on its pitch in either +/- direction
the other platforms will briefly "glitch" before "returning" to their normal euler angle-rotation.
This doesn't happen if the angle(yaw) or roll crosses the 90 degrees threshold, only pitch.
To be perfectly honest, I am not sure I'm describing this accurately. But you'll know it when you see it.Spoiler: TLDR code contents
If you just want the code and aren't interested in all the extra fluff, all you need is platform-base.zs
which contains the base/core class FishyPlatform, the special interpolation point FishyPlatformNode and
the pivot-mapspot FishyPlatformPivot.
FishyPlatform has all the logic for carrying actors, following interpolation point paths, grouping (attaching itself) with other FishyPlatform actors, and the pivot logic. FishyPlatform is an abstract class and was always meant to be inherited from.
FishyPlatformNode is a modded version of InterpolationPoint with extra options for FishyPlatform classes. And FishyPlatformPivot is just a glorified map spot that allows pivot setup without having to use ACS.
In addition, there is platform-generic.zs which contains a subclass of FishyPlatform called FishyPlatformGeneric. This subclass has the distinct ability to have its model, radius and height set through user variables. The radius and height can be set manually or they can be set automatically from the provided model. The advantage of using this is you don't need extra MODELDEF and actor definitions per model. The disadvantage is you won't see in UDB which model it's using until you load the map. You also won't see any size changes either.
Lastly, the ACS library (fplat.acs and fplat.o) that comes with this only contains wrapper functions which are just ScriptCall calls into FishyPlatform's ACS utility functions. The only point behind that is to make neater ACS code but otherwise you don't really need it.






