-
Notifications
You must be signed in to change notification settings - Fork 333
E2: Tips and Tricks
- Intro
- Lookup Tables
- Why changed doesn't do what you think it does
- Switch Case
- Persist a single table
- Coming Soon™:
On this page I will some you some cool tricks and techniques I learned over the years that can save you a lot of time, trouble and headaches.
Lookup tables are a easy way to access a multitude of values without complicated if statements or looping over arrays. Tables and arrays return the default "empty" value (0 for numbers) if there is no matching entry found.
The basics work like this:
# Create table once, using numbers as values
Table = table("someKey" = 1, "someOtherKey" = 42, "STEAM_0:0:12345"=1)
# Write to table
Table[Key, number] = Value
# Example usecases
if(Table:exists(Key)){ ... # Check if entry exists
if(Table[Key, number]){ ... # Check if entry is !=0 (and exists)
Value = Table[Key, number] ?: 1337 # Read value from table or return a default value if entry doesn't exist
Note that above examples use numbers, but you can make lookup tables for every value type, including other tables.
The single most common usecase is using a lookup table as whitelist to control access. Note that we use SteamID instead of player name or entity id, because it cannot be spoofed and is unaffected by rejoins. If you wanted you could even load the table contents from a file to keep as persistent friendlist for all your E2s.
@persist Whitelist:table # persist make variables keep their value between multiple executions, ie multiple chatClk runs
if(first()){
# add owner and two hardcoded ids
Whitelist = table( owner():steamID()=1, "STEAM_0:0:11111"=1, "STEAM_0:0:22222"=1)
}
...
# Example checking of whitelist
if(Whitelist:exists(Target:steamID())){
print("Target on Whitelist")
}
...
# Example of adding players by name (removing works similar, just set it to 0 if its on the list)
local Target = findPlayerByName( Input )
if(Whitelist:exists(Target:steamID())){
print("Already on whitelist")
}
else{
Whitelist[Target:steamID(), number] = 1
print("Added "+Target:name())
}
(To see how to make a chat command that targets players, check out this section.)
While we just use a value of 1 here, you can easily expand this to store some kind of access level, people with 1 may only use a single door, while people with 2 have access to all doors, and you could have admins with a value of 3 able to add or remove other users, or whatever custom system you can think of.
The same basic principle of using a table with SteamID can also be used to have a database, storing whatever data you can imagine for individual players, but this is left as an exercise to the reader.
In the same way that you can store numbers linked to SteamIDs for a whitelist, you can also store anything else linked to strings. The following example shows how you can use lookup tables to enable shortcuts or aliases for a train track spawner, so you don't have to use the full model name all the time.
@persist Models:table
if(first()){
Models = table(
"short_straight" = "models/sprops/trans/train/track_s01.mdl",
"straight" = "models/sprops/trans/train/track_s03.mdl",
"long_straight" = "models/sprops/trans/train/track_s06.mdl",
"tight_turn" = "models/sprops/trans/train/track_t90_01.mdl",
"wide_turn" = "models/sprops/trans/train/track_t90_01.mdl"
)
...
}
...
local Input = ... # Get input, maybe from chat command
# Check lookup table if there is a entry for the input. If the result is empty (no entry), just use the input directly
local FinalModel = Models[Input, string] ?: Input
propSpawn(FinalModel, ... , 1)
Doing it this way is much easier to expand than doing the same with ifs (equivalent code below):
local FinalModel = ""
if( Input == "short_straight" ){ FinalModel = "models/sprops/trans/train/track_s01.mdl" }
elseif( Input == "straight" ){ FinalModel = "models/sprops/trans/train/track_s03.mdl" }
...
elseif( Input == "wide_turn" ){ FinalModel = "models/sprops/trans/train/track_t90_01.mdl" }
else{ FinalModel = Input}
changed()
is one the hardest function in E2 to understand, even though it might seem straightforward.
Most people will probably think it tells you if the value changed since the last execution of the E2. And while that appears correct in most cases, it is not quite correct.
@input Button
if (Button) {
print("Hello")
if (changed(Button)) {
print("World")
}
}
timer("run again", 1000)
You might think this prints "Hello" and "World" every time the button is pressed (since Button changed from 0 to 1) and "Hello" while it is held down.
Instead it prints "World" only the very first time.
This is because it doesn't compare against the last tick/interval/whatever, but to the last call. And since changed
only gets called when the Button is 1 (due to the outer if
), changed
only "sees" a static value of 1. The first time works, because changed
start off with an internal value of 0.
Notes:
The same issue happens as well with if( Button & changed(Button) )
, because E2 optimizes logic operations so it doesn't even check the later parts (and therefore doesn't call changed
) when Button is 0, because 0 & <whatever>
will always be 0.
You could use if( changed(Button) & Button )
, but usually you want if(~Button & Button)
in that case (see the note about inputs below).
@input Button
for(I=1,3) {
if(changed(Button)) {
print(I)
}
}
timer("run again", 1000)
What do you think it will output whenever the button is pressed or released? 1, 2 and 3? Nope. Just 1.
This is because internally it keeps track of the last call value based on the position of the call in the code. So all three iterations of the loop check the same internal value. It sees the new value on the first iteration (and prints 1), but since the first iteration already updated the internal value, the second and third iteration see no change.
In this example you could just reorder the code (if(...){for(...){...}}
), but you might want to different values for the iterations...
Note:
This behavior not only applies to loops but also custom functions, even when when called from multiple places.
-
Inputs: In most cases you want to use the trigger operator (ie
~Button
), which checks if the E2 was triggered because the input changed. See E2: Triggers for more infos on E2s trigger system. -
Variable updates: When you use
changed
to check if you modified a value in some other part of the code (ie. to update some display), you could be using custom function to do your update right then and there, or (if you expect multiple values could update and you want to avoid multiple updates) use an additional variable as shared flag (ie. setShouldUpdate = 1
and check that instead). -
Arrays/Tables: Sorry, but there is no good way around doing what
changed
does yourself (but you can account for the index,changed
can not) - Keep a copy of the last values and compare against that, element by element (iefor(I=1,Length){if(Current[I,number]!=Last[I,number]){print(I+" changed"),Last[I,number]=Current[I,number]}}
).
E2 supports Switch Statements, which offer a more elegant solution to long if-elseif chains when you want to do different things based on the value of a single variable.
However, if you are just trying to assign different values to different inputs but the code is otherwise the same, a lookup table is an even better solution.
switch(Command){ # The variable to check
case "print", # do this when Command == "print"
print("Hello")
break # end of this block
case "reset", # multiple cases are allowed
case "restart", # do this when Command == "reset" or Command == "restart"
reset()
break
case "save_and_exit",
save_something() # notice the missing "break" here
case "exit", # if there is no break, it continues on with the next block
exit() # so exit() is ran in both cases, but save only in the top one
break
default, # ran when no case matches, this is optional
error("command not found")
}
On first glance it might look intimidating or complicated, but especially when you can put the multiple cases(reset/restart) or fallthrough (save_and_exit) to good use it can be simpler than the equivalent if-elseif chain:
if(Command == "print"){
print("Hello")
}
elseif(Command == "reset" | Command == "restart"){
reset()
}
elseif(Command == "save_and_exit" | Command == "exit"){
if(Command == "save_and_exit"){
save_something()
}
exit()
}
else{
error("Command not found")
}
You can find another example in the Chat Commands page.
When working on a larger project you probably have seen it: Your @persist
line is growing longer and longer, and maybe you already started a second or third row
Here is an example using a single table to store multiple settings and timing info for a customizable floating holo:
@name HoloBubble
@inputs Spawn
@persist P:table
if(first()){
# default settings
P=table(
"speed"=2,
"scale"=1,
"alpha"=127,
"material"="models/debug/debugwhite",
"lifetime"=50 )
runOnChat(1) # We'll need this later
}
elseif(~Spawn & Spawn){ # Button pressed
holoCreate(1, entity():pos(), vec(P["scale",number]), ang())
holoAlpha(1, P["alpha",number])
holoMaterial(1, P["material",string])
P["holo age",number]=0 # Just create new entries on the fly
stoptimer("move") # stop any old timers
timer("move", 100)
}
elseif(clk("move")){
if(P["holo age",number]<P["lifetime",number]){
holoPos(1, holoEntity(1):pos()+vec(0,0,P["speed",number]))
timer("move", 100)
}
else{
holoDelete(1) # delete holo after its too old
}
}
But being able to easily create new entries on the fly isn't the only advantage: Since it is a table you can also dynamically access the values, for example to change values on the fly, or quickly check the state for debugging:
elseif(chatClk(owner())){
local LS=owner():lastSaid():explode(" ")
if(LS[1,string]=="!set"){
hideChat(1)
local Key = LS[2, string]
local Value = LS[3, string]
local Type = P:typeids()[Key,string] # get type of value
switch(Type){
case "n",
P[Key,number]=Value:toNumber()
print("Set number '"+Key+"' to "+P[Key,number])
break
case "s",
P[Key,string]=Value
print("Set string '"+Key+"' to "+P[Key,string])
break
case "",
print("Key '"+Key+"' not found!")
break
default,
print("Changing type '"+Type+"' not implemented!")
}
}
elseif(LS[1,string]=="!debug"){
printTable(P)
}
[...]
But wait! Thats not all! You can also easily save and restore the settings:
[...]
elseif(LS[1,string]=="!save"){
local Output = "Bubblesettings:\n"+vonEncode(P) # Encode the table as string and add a header
fileWrite(Output, "bubblesettings_"+LS[2,string]+".txt") # save with given name
}
elseif(LS[1,string]=="!load"){
fileLoad("bubblesettings_"+LS[2,string]+".txt") # load with given name
runOnFile(1)
}
}
elseif(fileClk()){
local Content = fileRead()
if(Content:left(16)=="Bubblesettings:\n"){ # Check header of file
P = vonDecodeTable(Content:sub(17)) # just overwrite everthing at once
}
else{
print("File is not a valid save")
}
runOnFile(0)
}
Apart from that you can also do other stuff with the table, like sharing it with other E2s, wires or even transmiting it over the internet to your server (or load it from there) or use P:merge(Defaults)
to quickly overwrite a certain set of values.
You can also use multiple persisted tables for different uses, maybe you only want to save certain settings, but not other info which might be useless when loaded later (like the holo age from above), or you are making a game and only want to share certain information with other E2s, and keeps others private.
Of course you can also nest tables, this allows for easy grouping: P["settings",table]["color",vector]
, but when doing that you have to initialize the sub-table, for example: if(first()){ P=table(...), P["subtable",table]=table(), ...
Just keep in mind that you have to keep track of the entries yourself, as you will get no errors when you make typos, and a non-existant entry will just give you 0, an empty string, vec(0,0,0), etc depending on the type.
- shared tables
- custom security
- instant syncronisation
- fully transparent
- holo/egp id handling
- named ids
- table instead of copy/pasting holoCreate
- egp list/grid interfaces
- holo-only touchscreens
Please do not alter the e2 docs ...
pages manually.
They are autogenerated from the E2Helper. In the future, this will hopefully be its own dedicated website or tool.
Basic Features: core, debug, number, selfaware,
string, timer
🌎 World: angle, color, find, ranger, sound,
🔣 Math: bitwise, complex, matrix, quaternion, vector, vector2/4
📦 Entities: bone, constraint, egp, entity, hologram, npc
👨 Players: chat, console, player, weapon
📊 Data storage: array, files, globalvars, serialization, table
💬 Communication: datasignal, http, signal, wirelink,
❓ Informational: gametick, serverinfo, steamidconv, unitconv
Disabled by default: constraintcore, effects, propcore, remoteupload, wiring
Wire-Extras (repo): camera, ftrace, holoanim, light, stcontrol, tracesystem
- Home 🏠
- Syntax 🔣
- Directives 🎛️
- Editor 🖥️
- Events 🌟 (new!)
Click To Expand
- 🟥 SPU
- 🟥 Address Bus
- 🟥 Extended Bus
- 🟥 Plug/Socket
- 🟥 Port
- 🟥 Transfer Bus
- 🟩 GPU
- 🟥 Dynamic Memory
- 🟥 Flash EEPROM
- 🟥 ROM
- 🟧 Beacon Sensor
- 🟧 Locator
- 🟧 Target Finder
- 🟧 Waypoint
- 🟥 XYZ Beacon
- 🟩 CPU
- 🟩 Expression 2
- 🟩 Gates
- 🟥 PID
- 🟧 CD Disk
- 🟥 CD Ray
- 🟧 DHDD
- 🟥 Keycard
- 🟥 RAM-card
- 🟧 Satellite Dish
- 🟧 Store
- 🟧 Transferer
- 🟥 Wired Wirer
- 🟧 Adv Entity Marker
- 🟧 Damage Detector
- 🟧 Entity Marker
- 🟧 GPS
- 🟧 Gyroscope
- 🟥 HighSpeed Ranger
- 🟧 Laser Pointer Receiver
- 🟥 Microphone
- 🟧 Ranger
- 🟧 Speedometer
- 🟧 Water Sensor
- 🟧 7 Segment Display
- 🟥 Adv. Hud Indicator
- 🟧 Console Screen
- 🟧 Control Panel
- 🟧 Digital Screen
- 🟧 EGP v3
- 🟧 Fix RenderTargets
- 🟥 GPULib Switcher
- 🟧 Hud Indicator
- 🟧 Indicator
- 🟧 Lamp
- 🟧 Light
- 🟧 Oscilloscope
- 🟧 Pixel
- 🟧 Screen
- 🟧 Sound Emitter
- 🟧 Text Screen
- 🟩 Cam Controller
- 🟧 Colorer
- 🟧 FX Emitter
- 🟧 HighSpeed Holoemitter
- 🟧 HoloEmitter
- 🟧 HoloGrid
- 🟥 Interactable Holography Emitter
- 🟥 Materializer
- 🟥 Painter
- 🟧 Adv. Input
- 🟧 Button
- 🟧 Constant Value
- 🟥 Door Controller
- 🟧 Dual Input
- 🟧 Dynamic Button
- 🟧 Eye Pod
- 🟧 Graphics Tablet
- 🟧 Keyboard
- 🟥 Lever
- 🟧 Numpad
- 🟧 Numpad Input
- 🟧 Numpad Output
- 🟧 Plug
- 🟧 Pod Controller
- 🟧 Radio
- 🟧 Relay
- 🟧 Text Receiver
- 🟧 Two-way Radio
- 🟧 Vehicle Controller
- 🟥 Door
- 🟥 Adv. Dupe. Teleporter
- 🟥 Buoyancy
- 🟧 Clutch
- 🟧 Detonator
- 🟧 Explosives
- 🟧 Explosives (Simple)
- 🟥 Forcer
- 🟩 Freezer
- 🟧 Gimbal (Facer)
- 🟧 Grabber
- 🟧 Hoverball
- 🟧 Hoverdrive Controller
- 🟥 Hydraulic
- 🟧 Igniter
- 🟧 Nailer
- 🟩 Prop Spawner
- 🟥 Servo
- 🟥 Simple Servo
- 🟧 Thruster
- 🟥 Touchplate
- 🟥 Trail
- 🟩 Turret
- 🟩 User
- 🟥 Vector Thruster
- 🟥 Vehicle Exit Point
- 🟧 Weight (Adjustable)
- 🟧 Weld/Constraint Latch
- 🟥 Wheel
- 🟥 Wire Magnet
- 🟥 Wired Npc Controller
- 🟧 Debugger
- 🟥 GUI Wiring
- 🟥 Multi Wire
- 🟧 Namer
- 🟥 Simulate Data
- 🟩 Wiring
- 🟥 Beam Reader
- 🟥 Implanter
- 🟥 Reader
- 🟥 Target Filter
- 🟥 User Reader
Gates 🚥
Click To Expand
TBD