=[[cw4:custom| Index]]
====== qople - First Person Tools ======
//[[canonical link to CPACK]] - where to get the author-maintained version//
//This is the documentation page for the first person game mode. Information for players can be found [[e596dd71-a2a5-4e45-a25b-f293442a9d35|here]].//
The FPS scripts were made with extensibility and versatility in mind, but because of that some parts of them may seem a little daunting. This page's goal is to act as a guide and a reference to hopefully lower that barrier to making your own first person levels.
===== Basic Conventions =====
As a general rule, all weapon and attribute names must follow the same capitalization rules when used as inputs, no matter what script they appear in. Weapon names are all lowercase ("cannon") and attribute names are capitalized and camel cased ("MaxRange").
===== Weapon Attributes =====
Almost all weapon attributes have the same effect on all weapons. This holds true even if that weapon doesn't normally have that attribute. For instance, in "FPS: Anti Up" (map #586), the cannon is given AC simply by having an item upgrade give the cannon anticreep-related attributes.
Attributes that do not end in a ".0" are integers (they ignore anything past the decimal place when finding their effect).
^ Attribute Name ^ Default Value ^ Description ^ Notes ^
| Rate | 30 | The number of updates between shots. Values of 1 and below max the weapon fire rate, at once per update. | |
| DmgDepth | 0.0 | The depth of normal damage to creeper. Is affected by crimson (20% damage). | The same as the MaxNumCells, Radius, and Amt arguments to the [[4rpl:commands:damagecreeper|DamageCreeper]] function. |
| DmgDist | 0 | The maximum radius that normal damage will damage creeper at. | ::: |
| DmgMaxCells | 9999 | The maximum number of cells that normal creeper damage can affect. | ::: |
| DmgIterations | 1 | Increases normal damage spread against thin creep, decreasing damage loss. Multiplies the max cells when creeper is less than DmgDepth deep. | Implementation is to spread damage across several DamageCreeper calls with decreased amounts. |
| ProjSpeed | 10 | How fast the weapon's projectiles travel, in cells per update. | |
| MinRange | 0 | The lower bound of ballistic weapons' targeting, in cells. | Ballistic (mortar-like) weapons only. |
| MaxRange | 100 | The upper bound of ballistic weapons' targeting, in cells. | ::: |
| AcDepth | 0.0 | The depth of AC deposited per shot. | Same as the Amt, Range and Cap arguments to the [[4rpl:commands:addcreeperwithcapinrange|AddCreeperWithCapInRange]] function. |
| AcDist | 0 | The radius that AC is deposited inside of. | ::: |
| AcCap | 0.0 | The maximum depth that AC can be deposited to. Keeping the cap at 0 will add AC damage without creating AC, effectively giving bonus damage against crimson. | ::: |
| UnitDmgAmt | 0.0 | The amount of damage dealt to vulnerable enemy units. Most units have a max HP of 1. | |
| UnitDmgDist | 0.0 | The radius inside of which vulnerable enemy units will be damaged. | |
| MeshDmg | 0.0 | The amount of damage done to mesh. Mesh has a maximum health of 1. | |
| MeshDist | 0 | The radius inside of which mesh is damaged. | |
| BaseDPS | 0.0 | The DPS the weapon does by base, with no upgrades or modifiers. Changing this will make the numbers displayed for total weapon strength inaccurate, so only change this if you want that. | |
===== List of Units (CMODS) in this CPACK =====
* [[cw4:cmod:docs:d40712cb-4cb0-4034-bad6-a1c2d73ebaa8|DPS Monitor]]
* [[cw4:cmod:docs:fe1e9127-87ee-438a-a9b0-240365b93ce4|Wall Tool]]
* [[cw4:cmod:docs:d12d4163-732a-4b7c-b5ac-1dcf8f70aa1b|InfoCache Controller]]
===== Global Control Scripts =====
==== Move.4rpl ====
This script controls all aspects of first-person movement: speed, collision, respawn, etc. "Run when paused" should be turned on so it can return normal controls if the player leaves first person mode while paused.
Script vars:
* moveSpeed: The maximum speed the player can hit, in cells per update. High values will lead to clipping.
* accelTime: The number of updates it takes for the player to reach full speed from a standstill.
* decelTime: The number of updates it takes for the player to stop from full speed.
* playerHeight:The height of the player above the ground, in cells. The default is 9. Changing this in combination with the move speed will effectively change the scale of the map.
* allowMoveOnVoid: Whether void is counted as an impassable wall, or terrain height 0. Values other than 0 allow movement on void. Only really makes sense to allow it if the level has a planet.
* collisionThreshold: The highest terrain difference that the player can walk over.
* startX, startZ: The cell coordinates of the player's spawn and respawn point.
* startRotation: The direction that the player points when they spawn, in degrees. 0 faces them in the positive Z direction. Clock-Wise rotation.
* stunDuration: How many updates the player stays stunned for. Consecutive stuns do not stack.
==== Player.4rpl ====
Contains logic for health and firing weapons.
Script vars:
* maxHealth: The max health the player has. 30 is the default. Damage taken is proportional to the square root of creeper, with a depth of 1 dealing 0.1 damage per update.
* bonusRequirement: The percentage of health the player must stay above for the custom objective. Set to negative to disable.
* startingWeapons: A string containing a list of the weapons that are unlocked upon starting the level, separated by commas. Order doesn't affect this.
* weaponOrder: A list of all weapons that are in the level, in order of their hotkeys. Changing the order of this will rearrange the weapon hotkeys.
==== Vanilla Weapons.4rpl ====
A weapon pack that determines the starting settings for the cannon, mortar, and sprayer. It follows the same format that other weapon packs should, so this section applies to custom weapons as well.
All script vars except for those ending in "Other" simply set the starting value for the weapon and attribute in the name. See [[cw4:cpack:docs:23977d46-b69d-431a-82e1-f4013f889c5a#weapon_attributes|weapon attributes]] for a more detailed description.
Script vars ending in "Other" are for setting attributes that are not already listed for a particular weapon. The string is case sensitive, and must be formatted as a list of attribute-value pairs. The weapon name should not be added to the attribute name. An example would be:
DmgDepth: 0.5, DmgDist: 5, Rate: 10
Note that each key-value pair is separated with a colon, and different pairs are separated by commas. Whitespace is ignored.
==== Misc Settings.4rpl ====
Handles various settings that persist across the gamemode. Also creates the wall tool and DPS monitor on game load.
Script vars:
* useWallOverride: Whether to use a 2000 terrain override on cells with terrain 19 and 20. If it's on, updates all cells on game start or compile.
* wallUpdateRate: How often the wall overrides should be updated during game time. 1 checks the whole map every frame (not recommended), 90 checks over it every 3 seconds, and so on. Has no effect if useWallOverride is 0. Setting to 0 disables the updates, so the overrides will not change if terrain changes.
* chargeTotems: Makes all totems charge in a way usable in a first-person level. Rules are as follows:
* Totems will gain charge at an increasing rate while the player is within 15 cells.
* Partially charged totems will stop charging when the player is outside that radius, but will not lose charge.
* Totems will lose charge when there is creeper on the center cell of the unit.
* Disabled totems ("unit: off") will not charge, and any existing ammo will be removed.
* selectionMode: Controls selection and building behavior for hybrid levels. The options are: \\ 0: Default behavior \\ 1: Disables building in first person mode \\ 2: Disables building in and out of first person \\ 3: Same as 1, but also overrides selecting units \\ 4: Same as 2, but also overrides selecting units
* selectableWhitelist: A comma separated list of [[4rpl:commands:unit_types|unit types]] that are still selectable when unit selection is turned off by the script. The DPS monitor is internally added to this list.
* buildWhitelist: A comma separated list of unit types that are still buildable when building is turned off by the script.
===== 4RPL Extensions =====
//This section requires knowledge of 4RPL.//
==== Global Variables ====
Certain parts of the game mode are stored as [[4rpl:commands:specialsyntax#global_variables|global variables]], allowing them to be read from any script in the level. They are listed below.
^ Variable Name ^ Description ^
| activeWeapon | The name of the weapon that is currently selected. |
| fireTimer | The current countdown until the player can fire again. Mainly used by weapon packs. It can go below 0 if the player is not actively firing. |
| frameToUpdate | A float representing how many game updates pass for each frame. Ranges from 0.5 (1x speed with high FPS on) to 4 (4x speed with high FPS off). Scaling the speed of an effect done in [[4rpl:commands:msg_frameadvance|MSG_FrameAdvance]] by this amount makes it go in time with game updates while allowing smoother animations when high FPS is on. |
| isFPMode | A bool, indicating whether the player is currently in first person mode. Updates while paused. |
| playerForward | A normalized 3-dimensional vector pointing in the direction the player is looking. Changing this will //not// rotate the player. Useful for making something move in front of the player. |
| playerHealth | A floating point from 0 to 1 representing how much health the player has out of their max health. |
| playerPos | The position of the player as a 3-dimensional vector. It is the location of the camera when the player is in first person mode, not the terrain below the camera. This means it is roughly 9 cells above the terrain by default. Changing this will teleport the player to that x and z, but the y always follows the terrain. |
| playerStunned | A boolean indicating whether or not the player is stunned. |
| weaponAttributes | A table containing all attribute values for all weapons. More detail in [[23977d46-b69d-431a-82e1-f4013f889c5a#interacting_with_weapon_attributes|interacting with weapon attributes]] |
The Move.4rpl and Player.4rpl scripts are also registered, so you can get and set all variables they use with [[4rpl:commands:getregisteredscriptvar|GetRegisteredScriptVar]]. Move.4rpl is registered as "Move" and Player.4rpl is registered as "Player".
==== Messages ====
Various parts of the game are communicated via messages. Registering other scripts for these messages can sometimes be useful.
^ Message Channel ^ When it is sent ^ Data included ^
| ItemCollected | When an info cache is collected via the item.4rpl script. Also causes Player.4rpl to update its locally stored values for weapon attributes, so it should be called whenever the weapon attribute table is changed. | The ID of the unit that sent the message |
| PlayerDied | When the player's health reaches 0 | How many times the player has died |
| Respawn | When the respawn button is pressed | 0 |
| WeaponEffect | Whenever a weapon effect is created. Calling this will create the weapon effect. | A list: ["weaponName", position (a V3)] |
| WeaponFired | When the player fires any weapon | 0 |
| CrosshairUpdated | Every frame, after the crosshair's position is updated | The ID of the crosshair |
| Initialize | Whenever a weapon pack is initialized at the start of the game, before its weapons are added to the weaponAttributes table. | One weapon added by that script, which the script is also registered as. |
| DamagePlayer | Call to damage the player by a fixed amount. Negative values will heal the player. | The amount to damage the player |
| AddText | Call to display text on the bottom of the screen. Must be called every update while the text is shown. Calling it several times in one update adds a separate line for each. Useful for letting the player know something minor, or for debugging. | The text to display. If calling from a script that runs while paused, add "WP:" before the text to display. |
==== Interacting with Weapon Attributes ====
The weaponAttributes table has an entry for each weapon, accessible by its name. That value is //another// table with entries for all of that weapon's attributes. This means that the simplest syntax for reading the value of a weapon's attribute is (mind the capitalization):
<-*weaponAttributes {"weaponName"} {"AttributeName"}
Although if many attributes need to be read for the same weapon, it can be easier to store the second table to its own (local) variable.
Similarly, the simplest way to change a weapon's attribute is:
value ->*weaponAttributes {"weaponName"} {"AttributeName"}
=== Adding Custom Attributes ===
To put everything together, here is an example that uses everything mentioned so far.
Whenever a bullet creates its weapon effect, it sends a message on the "WeaponEffect" channel. _DATA will be a list with two elements: the first is the name of the weapon, and the second is a 3-dimensional vector of the location where the weapon effect was made. By registering a script for that event, custom effects can be added to weapons without modifying the original scripts.
The base template for this is as follows:
once
RegisterForMsg("WeaponEffect" "DoCustomWeaponEffects")
endonce
:DoCustomWeaponEffects
#Store data into more readable vars
<-_DATA[0] ->weapon
<-_DATA[1] ->effectPosition
#Read whatever attributes are needed for this effect
#Repeat if necessary
<-*weaponAttributes {<-weapon} {"SomeAttribute"} ->someAttribute
#Then create the effect. Teleport the player, flip C/AC, damp waves, whatever you want.
The weapon loader and item scripts don't filter what stats are being added to the weapon, so new attributes can be added and read just like the base ones.
==== Custom Weapons ====
New weapons can be added into the gamemode through the use of special scripts. There are two ways to write these scripts: for weapons that use the same logic as those in the "Vanilla Weapons" pack, defining starting stats and visuals is all that is needed. If a weapon needs to do more than what is possible with those options, you can define your own logic.
=== The Basic Way ===
When a weapon can use the same logic as the base weapons, creating that weapon becomes much simpler. This kind of custom weapon is more about creating a new weapon slot for a custom effect that doesn't fit on the regular weapons, like teleportation.
The things that must be addressed in a weapon pack are:
* Input vars/base stats
* Visual attributes
* Crosshair
* Custom effects, if any
Here is a blank template: (The Vanilla Weapons script can be used as an example)
#Comma separated, whitespace ignored.
#List of all weapons added by this script
$$weaponsAdded:""
#List of custom attributes those weapons should have on initialization
#Custom attributes in an "other" field are always included. This only matters for script input vars.
$$customAttributes:""
#List stat vars here. Format as weaponAttributeName. weaponOther is supported.
:Once
@SetHiddenAttributes
@InitializeWeapons
#Necessary if any new effects are added
RegisterForMsg("WeaponEffect" "DoCustomEffects")
#Make the weapon visually different from others
RegisterForMsg("CrosshairUpdated" "CrosshairAppearance")
:SetHiddenAttributes
#Hidden Stats (Affect visuals, not balance)
#Options are Color, Trail, FireSound, FireVolume, HitSound, HitVolume,
#EffectName, EffectScale, Path
<-value ->weaponColor #etc
:DoCustomEffects
#Put data into more readable vars
<-_DATA[0] ->weapon
<-_DATA[1] ->effectPosition
#Read whatever attributes are needed for this effect
#Repeat if necessary
<-*weaponAttributes {<-weapon} {"SomeAttribute"} ->someAttribute
#Then create the effect.
#Make a visual indicator by either modifying the existing crosshair's attributes
#Or by hiding it and making your own.
:CrosshairAppearance
<-_DATA ->crosshair
#==============
#Required for the weapon pack to work.
:InitializeWeapons
Split(RemoveWhiteSpace(<-weaponsAdded) ",") ->weaponsAdded
RegisterScript(<-weaponsAdded[0])
SendMsg("Initialize" <-weaponsAdded[0])
\\ Visual settings for weapons are in the form of hidden attributes. Some of them are vector types, meaning they cannot be set as input vars. They are:
^ Attribute Name ^ Default Value ^ Description ^ Notes ^
| Color | V3(1, 1, 1) | The color to make the bullet. Values over 1 will put the color into the HDR glow range. | |
| Trail | V2(0 0) | The characteristics of the bullet's trail. The same as the last two arguments to [[4rpl:commands:createtrail|CreateTrail]]. | |
| FireSound | "" | The name of the sound to play when fired. | A list of all valid sound names is [[4rpl:commands:sounds|here.]] |
| FireVolume | 1.0 | The volume the fire sound is played at. 1 is normal volume. | |
| HitSound | "" | The name of the sound to play when the bullet hits. | |
| HitVolume | 1.0 | The volume the hit sound is played at. 1 is normal volume. | |
| EffectName | "" | The name of the effect to make when the bullet hits. Leave empty for no effect. | See [[4rpl:commands:createeffect|CreateEffect]] for a list of options. |
| EffectScale | 1.0 | The scale the effect is made at. Applied to all dimensions. | |
| Path | 0 | The type of path and targeting the weapon uses. 0 is straight (cannon), and 1 is ballistic (mortar). If it is anything else, the crosshair will be hidden and the weapon will not fire on its own, leaving that up to you and giving more freedom. | |
| BulletGUID | "" | The GUID of a unit to create that replaces the default sphere mesh of the bullet. More below. | |
=== Using BulletGUID ===
If the BulletGUID attribute is defined for a weapon, the default bullet will be hidden, and a unit of the given type is created to act as the visual. This unit:
* Follows the same path the bullet would
* Is rotated to match the direction it is moving
* Is spawned with a variable named "parent" containing the unit ID of the bullet that created it
* Is destroyed when the bullet hits something or leaves the map
It can have scripts attached to it that make animations or provide additional weapon logic. BulletGUID does nothing for weapons that do not use default bullets.
=== The Advanced way ===
When a weapon's Path attribute is set to anything other than 0 or 1, nothing is done for you, meaning you have more freedom about what you do with the weapon, but it is more work to make. It's recommended that these weapons use a negative path to prevent overlap in case more path options are added for basic weapons.
The additional parts of the weapon that need to be addressed are:
* Weapon Targeting: should be done inside the CrosshairUpdated event handler if any visuals depend on this.
* Firing logic: Register for the WeaponFired message to do this. If weapon targeting fails, set the fireTimer global variable back to 0.
* Bullets: Normally the default bullet script figures out its own settings and what it should be doing based on the weapon attribute table and the active weapon. If you aren't using it, you need to do that.
The template for this is:
#Uses "myweapon" as a filler for name of the weapon.
#Comma separated, whitespace ignored.
#List of all weapons added by this script
$$weaponsAdded:""
#List of custom attibutes those weapons should have on initialization
#Custom attributes in an "other" field are always included. This only matters for script input vars.
$$customAttributes:""
#Disable default weapon logic
$$myweaponPath:-1
#List stat vars here. Format as weaponAttributeName. weaponOther is supported.
#===================
#basic weapon logic that should always run, if any
#For instance, if your weapon has limited charge it could recharge passively.
#===================
:once
@InitializeWeapons
@GetWeaponStats
RegisterForMsg("WeaponFired" "Fire")
RegisterForMsg("CrosshairUpdated" "UpdateCrosshair")
RegisterForMsg("ItemCollected" "GetWeaponStats")
#Fire the weapon
:Fire
#Only do something if your weapon is active
if(<-*activeWeapon "myweapon" eq)
#If the weapon is unable to fire, set <-*fireTimer to 0
#so the script doesn't start the weapon's cooldown.
endif
:UpdateCrosshair
if(<-*activeWeapon "myweapon" eq)
#Handle targeting in here if any visuals depend on it
else
#Disable or hide custom visuals while other weapons are active
endif
#Called whenever the ItemCollected message is sent.
#Stores the attributes of myweapon to variables with matching names.
#Note the variable names are Capitalized.
:GetWeaponStats
#If you don't need all the attributes, feel free to replace this with a smaller list.
<-*weaponAttributes{"myweapon"} GetTableKeys ->neededStats
<-neededStats GetListCount 0 do
<-neededStats[i] ->attribute
<-*weaponAttributes{"myweapon"}{<-attribute} <-attribute ->!
loop
#===================
#Required for the weapon pack to work.
:InitializeWeapons
Split(RemoveWhiteSpace(<-weaponsAdded) ",") ->weaponsAdded
RegisterScript(<-weaponsAdded[0])
SendMsg("Initialize" <-weaponsAdded[0])
\\ ==== Overriding Movement ====
Writing to the playerPos global will teleport the player. If high FPS is on, a tween frame will be added to make movement smoother. To disable that, set the W component of the vector (the 4th one) to true. To snap the height to the terrain, set the Y component to anything less than -100.
As soon as you stop changing the camera position, normal movement will return.
==== Misc. ====
Code snippets that might be useful for some purposes.
=== Unlocking a weapon ===
GetRegisteredScriptVar("Player" "unlockedWeapons") "weaponName" AppendToList
=== Changing the Respawn Point ===
SetRegisteredScriptVar("Move" "checkpoint" <-cellX <-cellZ FC)
<=[[cw4:custom| Index]]