Getting started • Basics • Lua Scripting • Data.wak • Useful Tools |
Audio • Enemies • Environments • Perks • Spells • Spritesheets • Materials • Image Emitters |
Lua API • Enums • Special Tags • List of all tags • Utility Scripts • Sound Events • Enemy Information Table • Spell and Perk IDs |
A collection of scripts created by other modders, which you can copy directly to your own mod, for example into your own utilities file: mods/<MY_MOD>/files/scripts/utilities.lua
Obtain the id of the player entity[]
This function obtains you the id of the player entity. Using it, you can extract data from components and other behaviors of the player.
-- simple utility function to return the player entity
function getPlayerEntity()
local players = EntityGetWithTag("player_unit")
if #players == 0 then return end
return players[1]
end
Easier entity debugging[]
function str(var)
if type(var) == 'table' then
local s = '{ '
for k,v in pairs(var) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. str(v) .. ','
end
return s .. '} '
end
return tostring(var)
end
function debug_entity(e)
local parent = EntityGetParent(e)
local children = EntityGetAllChildren(e)
local comps = EntityGetAllComponents(e)
print("--- ENTITY DATA ---")
print("Parent: ["..parent.."] " .. (EntityGetName(parent) or "nil"))
print(" Entity: ["..str(e).."] " .. (EntityGetName(e) or "nil"))
print(" Tags: " .. (EntityGetTags(e) or "nil"))
if (comps ~= nil) then
for _, comp in ipairs(comps) do
print(" Comp: ["..comp.."] " .. (ComponentGetTypeName(comp) or "nil"))
end
end
if children == nil then return end
for _, child in ipairs(children) do
local comps = EntityGetAllComponents(child)
print(" Child: ["..child.."] " .. EntityGetName(child))
for _, comp in ipairs(comps) do
print(" Comp: ["..comp.."] " .. (ComponentGetTypeName(comp) or "nil"))
end
end
print("--- END ENTITY DATA ---")
end
Use simply with debug_entity(entity)
Add custom names in translations[]
This piece of code is placed in the mod's init and is used to add custom translations:
local content = ModTextFileGetContent("data/translations/common.csv")
ModTextFileSetContent("data/translations/common.csv", content .. [[
item_custom,Custom Name,,,,,,,,,,,,,
]])
So if we were to name the entity "$item_custom" it's name in game would show up as "Custom Name"
Add new genomes (compatible with other mods)[]
The function add_new_genome
let's you append a new genome to the data/genome_relations.csv
file. Use it in your init.lua
.
(Keith and Horscht fixed this so now it finally works!)
function split_string(inputstr, sep)
sep = sep or "%s"
local t= {}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end
local content = ModTextFileGetContent("data/genome_relations.csv")
--The function works like this: genome_name is the name of your new genome/faction,
--default_relation_ab is the relation with all the horizontal genomes which relations weren't specified in the table,
--default_relation_ba is the relation with all the vertical genomes which relations weren't specified in the table,
--self relation is the genome's relation with itself,
--relations is a table which directly specifies the value of the genome relation with.
function add_new_genome(content, genome_name, default_relation_ab, default_relation_ba, self_relation, relations)
local lines = split_string(content, "\r\n")
local output = ""
local genome_order = {}
for i, line in ipairs(lines) do
if i == 1 then
output = output .. line .. "," .. genome_name .. "\r\n"
else
local herd = line:match("([%w_-]+),")
output = output .. line .. ","..(relations[herd] or default_relation_ba).."\r\n"
table.insert(genome_order, herd)
end
end
local line = genome_name
for i, v in ipairs(genome_order) do
line = line .. "," .. (relations[v] or default_relation_ab)
end
output = output .. line .. "," .. self_relation
return output
end
-- Example usage: (This sets all genome relations of this genome to 100, unless indicated so, in this case, I want 100 with everything, except the player and -1)
content = add_new_genome(content, "genome1", 100, 100, 100, {
player = 0,
["-1"] = 0
})
--Here I want 0 genome relations with everything, except for 100 with itself
content = add_new_genome(content, "genome2", 0, 0, 100, {})
ModTextFileSetContent("data/genome_relations.csv", content)
Add a new tag to an existing entity file[]
This function adds a tag to an existing entity without you having to overwrite existing entities:
function add_tags()
local xml2lua = dofile("mods/new_enemies/files/xml2lua_library/xml2lua.lua")
local xml_content = ModTextFileGetContent("data/entities/props/existing_entity.xml")
local handler = xml2lua.parse(xml_content)
if handler.root.Entity._attr.tags then
handler.root.Entity._attr.tags = handler.root.Entity._attr.tags .. ",your_tag_name"
else
handler.root.Entity._attr.tags = "your_tag_name"
end
local xml_output = xml2lua.toXml(handler.root, "Entity", 0)
ModTextFileSetContent("data/entities/props/existing_entity.xml", xml_output)
end
add_tags_seamine()
Rotate entity depending on topology of terrain[]
This script makes the entity rotate depending on terrain topology:
function approx_rolling_average(avg, new_sample, n)
avg = avg - avg / n;
avg = avg + new_sample / n;
return avg
end
local old_average = GetValueNumber("average_rotation", 0)
function get_direction( x1, y1, x2, y2 )
return math.atan2( ( y2 - y1 ), ( x2 - x1 ) )
end
local entity_id = GetUpdatedEntityID()
local x, y = EntityGetTransform(entity_id)
local found_normal, normal_x, normal_y, approximate_distance_from_surface = GetSurfaceNormal(x, y, 60, 16)
local dir = get_direction(0, 0, normal_x, normal_y)
local degree_shift = math.rad(-90) + dir -- 90 degree rotation to properly orient the entity
local new_average = approx_rolling_average(old_average, degree_shift, 5) -- if the number is higher than 5 the turning speed would be smoother and slower, if less it would be more instantaneous
SetValueNumber("average_rotation", new_average)
if found_normal then
EntitySetTransform(entity_id, x, y, new_average)
end
This script is then attached to the xml entity with a lua component, example:
<LuaComponent
_enabled="1"
script_source_file="mods/yourmod/files/the_script_above.lua"
execute_every_n_frame="1">
</LuaComponent>
Shoot Projectile from Entity Towards Player[]
This script, made by Evaisa, lets you shoot a projectile from an entity towards an entity.
dofile( "data/scripts/lib/utilities.lua" )
function rotate_degrees(x, y, degrees)
return rotate_radians(x, y, math.rad(degrees))
end
function rotate_radians(x, y, radians)
local ca = math.cos(radians)
local sa = math.sin(radians)
local out_x = ca * x - sa * y
local out_y = sa * x + ca * y
return out_x, out_y
end
function shoot_at_entity(self_entity, target_entity, general_offset, x_offset, y_offset, speed, projectile, maxdistance, max_player_distance, rotation_offset)
local rad_offset = 0
if(rotation_offset ~= nil)then
rad_offset = rotation_offset
end
local x, y = EntityGetTransform(self_entity)
if(target_entity ~= nil)then
local entityX, entityY = EntityGetTransform( target_entity )
local headingX = entityX - (x + x_offset)
local headingY = (entityY-5) - (y + y_offset)
local len = math.sqrt((headingX*headingX) + (headingY*headingY))
local directionX = headingX / len
local directionY = headingY / len
local didHit, hitX, hitY = RaytraceSurfaces( x, y, entityX, entityY )
local headingHitX = entityX - hitX
local headingHitY = entityY - hitY
local hitDistance = math.sqrt((headingHitX*headingHitX) + (headingHitY*headingHitY))
local pos_x, pos_y, currentRotation = EntityGetTransform( self_entity )
if(directionX < 0)then
EntitySetTransform(self_entity,pos_x,pos_y,currentRotation,-1,1)
else
EntitySetTransform(self_entity,pos_x,pos_y,currentRotation,1,1)
end
if(len < max_player_distance)then
if(hitDistance < maxdistance)then
directionX, directionY = rotate_degrees(directionX, directionY, rad_offset)
local ourProjectile = shoot_projectile( self_entity, projectile, x + (directionX * general_offset) + x_offset, y + (directionY * general_offset) + y_offset, directionX * speed, directionY * speed )
edit_component( ourProjectile, "ProjectileComponent", function(comp,vars)
vars.mWhoShot = self_entity
vars.mShooterHerdId = "player"
end)
end
end
end
end
To use this function, just do
shoot_at_entity([Attacker Entity], [Target Entity], [Projectile offset towards target], [Projectile Start X offset], [Projectile Start Y offset], [Projectile speed], [Projectile xml], [Distance to sense through walls], [Distance to see target])
Then attach a lua component to the entity xml which utilizes this script and runs every x frames, example:
<LuaComponent
script_source_file="mods/yourmod/files/previous_script.lua"
execute_every_n_frame="100"
>
</LuaComponent>
Attach entities to verlet chains[]
dofile_once("data/scripts/lib/utilities.lua")
local entity = GetUpdatedEntityID()
local _, _, day, hour, minute, second = GameGetDateAndTimeLocal()
SetRandomSeed(day + hour + second, minute + second + entity)
local verlet_data = {point_count = 0, positions = {}}
local x, y = EntityGetTransform(entity)
local verlet_entities = {}
local verlet_files = {}
local empty_percentage = 0
component_read( EntityGetFirstComponent( entity, "VerletPhysicsComponent" ), { positions = {} }, function(comp)
verlet_data.point_count = (#comp.positions / 2)
for i = 1, verlet_data.point_count, 4 do
local point = {x = 0, y = 0}
point.x, point.y = comp.positions[i], comp.positions[i + 1]
table.insert(verlet_data.positions, point)
end
end)
function set_verlet_position(verlet, x, y)
component_readwrite( EntityGetFirstComponent( verlet, "VerletPhysicsComponent" ), { positions = {} }, function(comp)
comp.positions[1] = x
comp.positions[2] = y
end)
end
function delimiter_split(input, delimiter)
local word_table = {}
for word in string.gmatch(input, '([^'..delimiter..']+)') do
table.insert(word_table, word)
end
return word_table
end
function tablelength(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end
local holder = nil
local variable_storage = EntityGetComponent(entity, "VariableStorageComponent")
for k, v in pairs(variable_storage)do
local name = ComponentGetValue2(v, "name")
if(name == "verlet_entity")then
table.insert(verlet_entities, tonumber(ComponentGetValue2(v, "value_string")))
elseif(name == "verlet_files")then
verlet_files = delimiter_split(ComponentGetValue2(v, "value_string"), ',')
elseif(name == "holder")then
holder = tonumber(ComponentGetValue2(v, "value_string"))
end
end
for k, position in pairs(verlet_data.positions)do
if(verlet_entities[1] ~= nil)then
if(verlet_entities[k] ~= 0)then
if(EntityGetIsAlive(verlet_entities[k]))then
EntitySetTransform(verlet_entities[k], position.x, position.y)
set_verlet_position(verlet_entities[k], position.x, position.y)
end
end
else
if(0 == empty_percentage)then
-- for k, position in pairs(verlet_data.positions)do
-- if k % 2 == 0 then
local verlet_file = verlet_files[Random(1, #verlet_files)]
local verlet = EntityLoad(verlet_file, x, y)
EntityAddChild(entity, verlet)
if(verlet ~= nil)then
EntityAddComponent(entity, "VariableStorageComponent", {
name="verlet_entity",
value_string=tostring(verlet),
})
-- GamePrint("holder = "..holder)
if(holder ~= 0)then
EntityAddComponent(holder, "VariableStorageComponent", {
name="verlet_entity",
value_string=tostring(verlet),
})
end
end
-- end
-- end
else
EntityAddComponent(entity, "VariableStorageComponent", {
name="verlet_entity",
value_string="0",
})
end
end
end
Add this to the verlet chain entity like this:
<VariableStorageComponent
name="verlet_files"
value_string="mods/yourmod/files/entity_you_want_to_attach_to_verlet.xml"
></VariableStorageComponent>
<LuaComponent
_enabled="1"
script_source_file="mods/yourmod/files/the_lua_script_above.lua"
execute_on_added="1"
execute_every_n_frame="0">
</LuaComponent>
This will add one entity to each verlet chain segment! Keep in mind that it is heavy on performance.
Rotate physics object towards player[]
dofile_once("data/scripts/lib/utilities.lua")
local this_entity = GetUpdatedEntityID()
local x, y, r = EntityGetTransform(this_entity)
local variable_storage = EntityGetComponent(this_entity, "VariableStorageComponent")
local aim_direction = {x = 0, y = 0}
local players = EntityGetWithTag("player_unit")
if #players > 0 then
local player_x, player_y = EntityGetTransform(players[1])
local direction_x, direction_y = player_x - x, player_y - y
local len = math.sqrt((direction_x*direction_x) + (direction_y*direction_y))
aim_direction.x, aim_direction.y = direction_x / len, direction_y / len
local phys_body = EntityGetFirstComponent(this_entity, "PhysicsBodyComponent")
local ang_vel = PhysicsGetComponentAngularVelocity( this_entity, phys_body )
function angle_rad(direction)
return math.atan2(direction.y,direction.x)
end
function get_torque(current_angle, target_angle, speed)
-- Make angle go from 0 to 2pi
if target_angle < 0 then
target_angle = target_angle + math.pi * 2
end
if current_angle < 0 then
current_angle = current_angle + math.pi * 2
end
local dist_up = math.abs(target_angle - current_angle)
local dist_down = math.abs((current_angle + math.pi * 2) - target_angle)
if current_angle > target_angle then
dist_up = math.abs((target_angle + math.pi * 2) - current_angle)
dist_down = math.abs(current_angle - target_angle)
end
local dir
if math.abs(dist_up) < math.abs(dist_down) then
dir = 1
else
dir = -1
end
local dist_smallest = dist_up
if dist_down < dist_up then
dist_smallest = dist_down
end
local new_angle = speed * dir
return new_angle
end
PhysicsApplyTorque( this_entity, -ang_vel )
PhysicsApplyTorque( this_entity, get_torque(r, angle_rad(aim_direction), 5) )
end
This script is then attached to the xml entity with a lua component, example:
<LuaComponent
_enabled="1"
script_source_file="mods/yourmod/files/the_script_above.lua"
execute_every_n_frame="1">
</LuaComponent>
For this to work it is important to remove any torque from the physics AI of the entity bearing the lua component by setting torque to 0 in the physics AI component.
<PhysicsAIComponent
torque_balancing_coeff="0"
torque_coeff="0"
torque_max="0" >
</PhysicsAIComponent>
Check if inside wall script[]
This function checks if you're spawning things in walls.
function is_inside_wall(x, y, radius)
for i=1,8 do
local slice = math.pi * 2 / 8 * i
local did_hit, hit_x, hit_y = RaytraceSurfacesAndLiquiform(x, y, x + math.cos(slice) * radius, y + math.sin(slice) * radius)
if did_hit then
return true
end
end
return false
end
if not is_inside_wall(x, y, 32) then
-- Whatever you want to do!
end
Player movement checking[]
Use these functions to obtain flags regarding the player's movement.
-- returns "right" if the player is moving right, "left" if the player is moving left, or an empty string if neither
function getPlayerHorizontalMovement()
local vel_x, vel_y = getPlayerVelocities()
local horizontal_movement = ""
if(vel_x > 10) then
horizontal_movement = "right"
end
if(vel_x < -10) then
horizontal_movement = "left"
end
return horizontal_movement
end
-- returns "down" if the player is falling, "up" if the player is going up, or an empty string if neither
function getPlayerVerticalMovement()
local vel_x, vel_y = getPlayerVelocities()
local vertical_movement = ""
if(vel_y > 0 and vel_y ~= 60) then
vertical_movement = "down"
end
if(vel_y < 0) then
vertical_movement = "up"
end
return vertical_movement
end
-- returns true if the player is moving, or false otherwise
function isPlayerMoving()
local flag = false
local x, y = EntityGetTransform( getPlayerEntity() )
local movement_threshold = 0.1
if old_position and (math.abs(x - old_position.x) > movement_threshold or math.abs(y - old_position.y) > movement_threshold) then
flag = true
end
old_position = {x=x, y=y};
return flag
end
-- returns true if the player is standing on solid ground, or false if in the air
function isPlayerStanding()
local vel_x, vel_y = getPlayerVelocities()
if(vel_y == 60) then -- default gravity value when the player is standing on something solid
return true
else
return false
end
end
-- obtains x and y velocities from the player
function getPlayerVelocities()
local cdc_id = EntityGetFirstComponentIncludingDisabled(getPlayerEntity(), "CharacterDataComponent")
local velocity_x, velocity_y = ComponentGetValue2(cdc_id, "mVelocity")
return velocity_x, velocity_y
end
Obtaining and setting gold amount[]
These functions can be used to manipulate the amount of gold owned by the player.
-- function to add or subtract gold from player
-- amount can be either negative or positive
function addPlayerGold(amount)
local player = getPlayerEntity()
local wallet = EntityGetFirstComponent(player, "WalletComponent")
local money = ComponentGetValueInt(wallet, "money")
local player_money = money + amount
edit_component(player, "WalletComponent", function(comp,vars)
vars.money = player_money
end)
end
-- function to get player gold
function getPlayerGold()
local player = getPlayerEntity()
local wallet = EntityGetFirstComponent(player, "WalletComponent")
local money = ComponentGetValueInt(wallet, "money")
return money
end
Internal variable manipulation[]
The following functions can be used to obtain or update internal variables present in a variable storage component of an entity. Both get and set functions assume the variable name you are trying to get or set already exists in the entity you are providing.
-- function to obtain a value from an internal variable contained in a variable storage component
-- entity_id is the id of the entity whose internal variables will be accessed
-- variable_name is the name of the internal variable to be accesses
-- variable_type is type indicator of the internal variable, which can be value_string, value_int or value_float
function getInternalVariableValue(entity_id, variable_name, variable_type)
local value = nil
local components = EntityGetComponent( entity_id, "VariableStorageComponent" )
if ( components ~= nil ) then
for key,comp_id in pairs(components) do
local var_name = ComponentGetValue2( comp_id, "name" )
if(var_name == variable_name) then
value = ComponentGetValue2(comp_id, variable_type)
end
end
end
return value
end
-- function to set a value of an internal variable contained in a variable storage component of an entity with entity_id with name variable_name and type variable_type
-- entity_id is the id of the entity whose internal variables will be accessed
-- variable_name is the name of the internal variable to be accesses
-- variable_type is type indicator of the internal variable, which can be value_string, value_int or value_float
function setInternalVariableValue(entity_id, variable_name, variable_type, new_value)
local components = EntityGetComponent( entity_id, "VariableStorageComponent" )
if ( components ~= nil ) then
for key,comp_id in pairs(components) do
local var_name = ComponentGetValue2( comp_id, "name" )
if( var_name == variable_name) then
ComponentSetValue2( comp_id, variable_type, new_value )
end
end
end
end
-- function to add new internal variables to an entity
-- entity_id is the id of the entity that will receive a new internal variable
-- variable_type is the type of variable being added, can be value_int, value_string or value_float
-- initial_value is the starting value of the variable being added, if none, must be provided as, e.g., 0, "", or 0.0
function addNewInternalVariable(entity_id, variable_name, variable_type, initial_value)
if(variable_type == "value_int") then
EntityAddComponent2(entity_id, "VariableStorageComponent", {
name=variable_name,
value_int=initial_value
})
elseif(variable_type == "value_string") then
EntityAddComponent2(entity_id, "VariableStorageComponent", {
name=variable_name,
value_string=initial_value
})
elseif(variable_type == "value_float") then
EntityAddComponent2(entity_id, "VariableStorageComponent", {
name=variable_name,
value_float=initial_value
})
elseif(variable_type == "value_bool") then
EntityAddComponent2(entity_id, "VariableStorageComponent", {
name=variable_name,
value_bool=initial_value
})
end
end
Player Health manipulation[]
The following functions can be used to manipulate and obtain hp from the player.
-- function to return player health
-- health values are treated internally as floats, so if the player has 5 health, the internal value will be 0.008
function getPlayerHealth()
local damagemodels = EntityGetComponent( getPlayerEntity(), "DamageModelComponent" )
local health = 0
if( damagemodels ~= nil ) then
for i,v in ipairs(damagemodels) do
health = tonumber( ComponentGetValue( v, "hp" ) )
--GamePrint(health)
break
end
end
return health
end
-- function to return player max health
function getPlayerMaxHealth()
local damagemodels = EntityGetComponent( getPlayerEntity(), "DamageModelComponent" )
local maxHealth = 0
if( damagemodels ~= nil ) then
for i,v in ipairs(damagemodels) do
maxHealth = tonumber( ComponentGetValue( v, "max_hp" ) )
break
end
end
return maxHealth
end
-- function to add to player health
-- amount is how much is player health to be added to, may be positive or negative
-- amount must be provided as an integer
-- overheal_flag tells the function if amount can update maximum health when amount added goes beyond it
function addPlayerHealth(amount, overheal_flag)
local damagemodels = EntityGetComponent( getPlayerEntity(), "DamageModelComponent" )
if( damagemodels ~= nil ) then
for i,v in ipairs(damagemodels) do
currentHealth = tonumber( ComponentGetValue( v, "hp" ) )
currentMaxHealth = tonumber( ComponentGetValue( v, "max_hp") )
local trueAmount = amount * 0.04 -- how health scaling works when shown by UI
local updatedHealth = currentHealth + trueAmount
local updatedMaxHealth = 0
local updatedHealth = currentHealth + trueAmount
if(updatedHealth > currentMaxHealth and overheal_flag) then -- overhealing, if new health goes beyond max health, max health is updated
updatedMaxHealth = updatedHealth
ComponentSetValue( v, "max_hp", updatedMaxHealth)
ComponentSetValue( v, "hp", updatedHealth)
else
if(updatedHealth <= 0) then
updatedHealth = 0.04
end
ComponentSetValue( v, "hp", updatedHealth) -- sets health to 1 when updatedHealth is zero or below
end
break
end
end
end
-- function to directly set a value to player health
-- newHealth is the new health the player is going to have
-- newHealth must be provided as a float value
function setPlayerHealth(newHealth)
local damagemodels = EntityGetComponent( getPlayerEntity(), "DamageModelComponent" )
if( damagemodels ~= nil ) then
for i,v in ipairs(damagemodels) do
ComponentSetValue( v, "hp", newHealth)
end
end
end
Get and set basic damage multipliers[]
The functions below are useful to obtain and modify damage multipliers of any creature with a valid id.
-- get all basic damage multipliers from entity with entity_id
function getBasicDamageMultipliers(entity_id)
local dmc_id = EntityGetFirstComponentIncludingDisabled(entity_id, "DamageModelComponent")
local ice = ComponentObjectGetValue2( dmc_id, "damage_multipliers", "ice" )
local fire = ComponentObjectGetValue2( dmc_id, "damage_multipliers", "fire" )
local drill = ComponentObjectGetValue2( dmc_id, "damage_multipliers", "drill" )
local slice = ComponentObjectGetValue2( dmc_id, "damage_multipliers", "slice" )
local melee = ComponentObjectGetValue2( dmc_id, "damage_multipliers", "melee" )
local projectile = ComponentObjectGetValue2( dmc_id, "damage_multipliers", "projectile" )
local radioactive = ComponentObjectGetValue2( dmc_id, "damage_multipliers", "radioactive" )
local explosion = ComponentObjectGetValue2( dmc_id, "damage_multipliers", "explosion" )
local electricity = ComponentObjectGetValue2( dmc_id, "damage_multipliers", "electricity" )
return ice, fire, drill, slice, melee, projectile, radioactive, explosion, electricity
end
-- gets the value for a damage multiplier of an entity with entity_id
-- multiplier_name must be either ice, fire, drill, slice, melee, projectile, radioactive, explosion or electricity
function getBasicDamageMultiplier(entity_id, multiplier_name)
local dmc_id = EntityGetFirstComponentIncludingDisabled(entity_id, "DamageModelComponent")
local multiplier = ComponentObjectGetValue2( dmc_id, "damage_multipliers", multiplier_name )
return multiplier
end
-- sets a new value for a damage multiplier of an entity with entity_id
-- multiplier_name must be either ice, fire, drill, slice, melee, projectile, radioactive, explosion or electricity
function setBasicDamageMultiplier(entity_id, multiplier_name, new_multiplier)
local dmc_id = EntityGetFirstComponentIncludingDisabled(entity_id, "DamageModelComponent")
ComponentObjectSetValue2( dmc_id, "damage_multipliers", multiplier_name, new_multiplier )
end
Verlet Carrion Legs Script - by Horscht[]
This script gives your entity verlet tentacles which attach to walls like in the game Carrion.
dofile_once("data/scripts/lib/utilities.lua")
------- CONFIG------
local num_limbs = 5
local angle_spread = 220
local cast_length = 200
local move_speed = 5
local resting_length = 20
--------------------
local entity_id = GetUpdatedEntityID()
local x, y = EntityGetTransform(entity_id)
local last_x = GetValueNumber("last_x", x)
local last_y = GetValueNumber("last_y", y)
local vx, vy = x - last_x, y - last_y
local last_direction = GetValueNumber("last_direction", 0)
local direction = math.atan2(vy, vx)
if math.abs(vx) + math.abs(vy) > 0.1 then
direction = rot_lerp(direction, last_direction, 0.05)
else
direction = last_direction
end
SetValueNumber("last_direction", direction)
SetValueNumber("last_x", x)
SetValueNumber("last_y", y)
SetRandomSeed(x + entity_id, y)
if not GetValueBool("initialized", false) then
SetValueBool("initialized", true)
for i=1, num_limbs do
local ent = EntityLoad("data/entities/verlet_chains/vines/verlet_vine_pixelscene.xml")
EntityAddComponent2(ent, "VerletWorldJointComponent")
EntitySetName(ent, "tentacle_limb")
SetValueNumber("jiggle_speed_"..tostring(i), Randomf(1, 3))
EntityAddChild(entity_id, ent)
end
end
local i = 0
for _, child in ipairs(EntityGetAllChildren(entity_id)) do
if EntityGetName(child) ~= "tentacle_limb" then goto continue end
i = i + 1
-- The limb's current position
local x2, y2 = GetValueNumber("x_"..tostring(i), x), GetValueNumber("y_"..tostring(i), y)
-- The position it's currently moving towards
local target_x, target_y = GetValueNumber("target_x_"..tostring(i), x), GetValueNumber("target_y_"..tostring(i), y)
local anchor_found = GetValueBool("anchor_found_"..tostring(i), false)
local cast_direction_x, cast_direction_y = vec_rotate(math.cos(direction), math.sin(direction), math.rad(-angle_spread/2 + (i-1) * angle_spread / (num_limbs-1)))
local move_x, move_y = vec_normalize(target_x - x2, target_y - y2)
x2 = x2 + move_x * move_speed
y2 = y2 + move_y * move_speed
if math.abs(x2 - target_x) < 5 then
x2 = target_x
end
if math.abs(y2 - target_y) < 5 then
y2 = target_y
end
if not anchor_found then
SetValueNumber("target_x_"..tostring(i), x + cast_direction_x * resting_length)
SetValueNumber("target_y_"..tostring(i), y + cast_direction_y * resting_length)
end
-- Only do the raytracing stuff every x frames because it's probably expensive
if GameGetFrameNum() % 20 == 0 then
-- Check if there's still a pixel to hold on to
local pixel_found = GetSurfaceNormal(target_x, target_y, 2, 16)
if not pixel_found then
anchor_found = false
end
-- target_hit checks if there's an obstacle between the main body and target
local target_hit, target_hit_x, target_hit_y = RaytraceSurfaces(x, y, target_x - cast_direction_x * 2, target_y - cast_direction_y * 2)
local distance_to_target = get_distance(x, y, target_x, target_y)
-- Check if anchor is still in sight and range
if not anchor_found or target_hit or (anchor_found and distance_to_target > cast_length) then
-- Lost sight of anchor or anchor too far away, look for new anchor
local hit, hit_x, hit_y = RaytraceSurfaces(x, y, x + cast_direction_x * cast_length, y + cast_direction_y * cast_length)
if hit then
-- Found new anchor, set target to it
SetValueNumber("target_x_"..tostring(i), hit_x)
SetValueNumber("target_y_"..tostring(i), hit_y)
SetValueBool("anchor_found_"..tostring(i), true)
else
SetValueBool("anchor_found_"..tostring(i), false)
end
end
end
SetValueNumber("x_"..tostring(i), x2)
SetValueNumber("y_"..tostring(i), y2)
local verlet_component = EntityGetFirstComponentIncludingDisabled(child, "VerletPhysicsComponent")
local verlet_world_joint_component = EntityGetFirstComponentIncludingDisabled(child, "VerletWorldJointComponent")
local positions = ComponentGetValue2(verlet_component, "positions")
local add_wave = anchor_found and 0 or 3
local jiggle_speed = GetValueNumber("jiggle_speed_"..tostring(i), 1)
positions[27] = x2 + math.cos(GameGetFrameNum() * 0.02 + jiggle_speed) * add_wave
positions[28] = y2 + math.sin(GameGetFrameNum() * 0.05 + jiggle_speed) * add_wave
ComponentSetValue2(verlet_component, "positions", positions)
-- world_position is the origin of the verlet
ComponentSetValue2(verlet_world_joint_component, "world_position", x, y)
::continue::
end
You just put this script in a lua component on the entity you want to have the tentacles like this:
<LuaComponent
script_source_file="mods/your_mod/files/scripts/this_script.lua"
execute_on_added="1"
execute_every_n_frame="1"
>
</LuaComponent>
Find if the player is polymorphed - by Soler91[]
This returns both a bool and the player entity if the player is indeed polymorphed; otherwise it returns nil.
function IsPlayerPolymorphed() -- returns bool, entityId/nil
local polymorphed_entities = EntityGetWithTag("polymorphed")
if (polymorphed_entities ~= nil) then
for _, entity_id in ipairs(polymorphed_entities) do
local is_player = false
local game_stats_comp = EntityGetFirstComponent(entity_id, "GameStatsComponent")
if (game_stats_comp ~= nil) then
is_player = ComponentGetValue2(game_stats_comp, "is_player")
end
if (is_player) then
return true, entity_id
end
end
return false, nil
end
end
Modify XML files[]
To modify XML files, it's best to use Zatherz' XML parser library: https://github.com/zatherz/luanxml
First, you get the contents of the XML you want to modify using ModTextFileGetContent
, then you parse that using the XML parser, modify it (check the Github page for documentation on that) and write it back using ModTextFileSetContent
, to convert the NXML object back to a string for saving, use tostring()
.
Keep in mind that the ModTextFileGet/SetContent
functions only exist in init.lua
and disappear after OnMagicNumbersAndWorldSeedInitialized
has run, so you need to do this before that. For instance simply outside any function, at the global level in init.lua
Here is some example code for how to change the EDR(Extremely Dense Rock)'s durability to 5 in the data/materials.xml
file.
local nxml = dofile_once("nxml.lua")
local content = ModTextFileGetContent("data/materials.xml")
local xml = nxml.parse(content)
for element in xml:each_child() do
if element.attr.name == "rock_hard_border" then
element.attr.durability = 5
end
end
ModTextFileSetContent("data/materials.xml", tostring(xml))
Or here's how to add a biome to data/biome/_biomes_all.xml
:
local nxml = dofile_once("nxml.lua")
local content = ModTextFileGetContent("data/biome/_biomes_all.xml")
local xml = nxml.parse(content)
xml:add_child(nxml.parse([[
<Biome
biome_filename="mods/yourmod/files/biomes/your_biome.xml"
height_index="0"
color="ff123456">
</Biome>
]]))
ModTextFileSetContent("data/biome/_biomes_all.xml", tostring(xml))
Constrain an entity to a set of coordinates - Thatrius[]
This pulls the effected entity back if it gets too far away. Works with physics objects.
function constrain(entity, x2, y2, max_dist)
local chardatacomp = EntityGetFirstComponent(entity, "CharacterDataComponent")
local projcomp = EntityGetFirstComponent(entity, "VelocityComponent")
local physcomp = EntityGetFirstComponent(entity, "PhysicsBodyComponent") or EntityGetFirstComponent(entity1, "PhysicsBody2Component") or EntityGetFirstComponent(entity1, "SimplePhysicsComponent")
local vel_x, vel_y = ComponentGetValue2(chardatacomp, "mVelocity")
local proj_vel_x, proj_vel_y = ComponentGetValue2(projcomp, "mVelocity")
local x1, y1 = EntityGetTransform(entity)
if (not x1) or (not x2) then return nil end
local dist = math.sqrt(((x2-x1)^2) + ((y2-y1)^2))
local target_x = x2 + (((x1-x2)/dist)*max_dist)
local target_y = y2 + (((y1-y2)/dist)*max_dist)
local target_vel_x = target_x - x1
local target_vel_y = target_y - y1
if dist > max_dist then
if physcomp or not proj_vel_x then
PhysicsApplyForce(entity, target_vel_x*100, target_vel_y*100)
end
if vel_x then
vel_y = vel_y - 60
local target_mag = math.sqrt((target_vel_x^2) + (target_vel_y^2))
local mag = math.sqrt((vel_x^2) + (vel_y^2)) / 40
local force_x = ((target_vel_x/target_mag)*mag)
local force_y = ((target_vel_y/target_mag)*mag)
vel_x = ((vel_x + force_x)*1.025) + (target_vel_x*dist*0.8)
vel_y = ((vel_y + force_y)*1.025) + (target_vel_y*dist*0.8)
EntitySetTransform(entity, target_x, target_y)
if ComponentGetValue2(chardatacomp, "is_on_ground") == false then vel_x = vel_x*1.16 end
ComponentSetValue2(chardatacomp, "mVelocity", vel_x, vel_y+60)
elseif proj_vel_x then
ComponentSetValue2(projcomp, "mVelocity", proj_vel_x+(target_vel_x*100), proj_vel_y+(target_vel_y*100))
EntitySetTransform(entity, target_x, target_y)
end
end
end
To constrain two entities to each other, just run it on each of them using the other's coordinates:
x1, y1 = EntityGetTransform(entity1)
x2, y2 = EntityGetTransform(entity2)
constrain(entity1, x2, y2, 50)
constrain(entity2, x1, y1, 50)
Inverse Kinematics script (works with more than 2 limb parts)[]
This glorious script was wrought into being by Thatrius:
---------------------------------------------------------------------------------
--Define Functions
---------------------------------------------------------------------------------
function distance(x1,y1,x2,y2)
return math.sqrt((x1 - x2)^2 + (y1 - y2)^2)
end
function approx_rolling_average(avg, target, n)
avg = avg - avg / n;
avg = avg + target / n;
return avg
end
local smoothness_factor = 6
---------------------------------------------------------------------------------
--General Setup
---------------------------------------------------------------------------------
local entity = GetUpdatedEntityID()
local ex, ey = EntityGetTransform(entity)
local parent = EntityGetParent(entity)
local vel_x, vel_y = GameGetVelocityCompVelocity(parent)
local vel_mag = math.sqrt((vel_x)^2 + (vel_y)^2)
local futurex, futurey = ex+(vel_x*60), ey+(vel_y*60)
local lengthcomp = EntityGetFirstComponent(entity, "VariableStorageComponent", "length")
local target = {GetValueNumber("targ_x", ex), GetValueNumber("targ_y", ey)}
local length = ComponentGetValue2(lengthcomp, "value_int")
local time = GetValueNumber("time", 0)
---------------------------------------------------------------------------------
--Inverse Kinematics
---------------------------------------------------------------------------------
--organize the segments and joints into neat little tables:
local legments = {} --"leg" + "segments"... haha funny word combo do you get
local joints = {}
local knees = {}
for i, child in ipairs(EntityGetAllChildren(entity)) do
if not EntityHasTag(child, "knee") then
local x, y = EntityGetTransform(child)
if x == 0 then x = 1 end --safeguards against stupid "nan"s by making sure the code can never divide 0/0. Lost a piece of my soul in the frustration brought about by trying to track down a bug cause by this. Why does 0/0 have to equal "nan" in lua anyways and not just 0 or something, it serves no purpose except to create frustration, this is utterly retarted
if y == 0 then y = 1 end
table.insert(legments, child)
if i == 1 then table.insert(joints, {ex, ey}) else table.insert(joints, {x, y}) end
else
table.insert(knees, child)
end
end
table.insert(joints, target)
--First pass, starting at end of chain:
for i = #joints, 1, -1 do
if i == #joints then
--move joint directly to the target coords:
joints[i] = target
elseif i ~= 1 then
--move joint within range of the previous one iterated:
local jx, jy = joints[i][1], joints[i][2]
local jx2, jy2 = joints[i+1][1], joints[i+1][2]
if jx == jx2 or jy == jy2 then jx, jx = jx+1, jy+1 end --more nan-avoiding bullcrap
local joint_dist = distance(jx, jy, jx2, jy2)
local jx = jx2 + (((jx-jx2) / joint_dist) * length)
local jy = jy2 + (((jy-jy2) / joint_dist) * length)
joints[i] = {jx, jy}
end
end
--Second pass, starting at beginning of chain:
for i, joint in ipairs(joints) do
if i ~= 1 then
--move joint within range of the previous one iterated:
local jx, jy = joints[i][1], joints[i][2]
local jx2, jy2 = joints[i-1][1], joints[i-1][2]
if jx == jx2 or jy == jy2 then jx, jx = jx+1, jy+1 end
local joint_dist = distance(jx, jy, jx2, jy2)
local jx = jx2 + (((jx-jx2) / joint_dist) * length)
local jy = jy2 + (((jy-jy2) / joint_dist) * length)
joints[i] = {jx, jy}
end
end
--Arrange all the legments to connect in between the joints:
for i, legment in ipairs(legments) do
local jx, jy = math.floor(joints[i][1]), math.floor(joints[i][2])
local jx2, jy2 = joints[i+1][1], joints[i+1][2]
if jx == jx2 or jy == jy2 then jx, jx = jx+1, jy+1 end
local vec_x, vec_y = jx2-jx, jy2-jy
local targ_rot = math.atan2(vec_y, vec_x)
EntitySetTransform(legment, jx, jy, targ_rot)
if knees[i] then EntitySetTransform(knees[i], jx2, jy2) end
end
---------------------------------------------------------------------------------
--AI:
---------------------------------------------------------------------------------
local total_leglength = length*#legments --combined length of all the segments in the leg
local length_tolerance = total_leglength--how far to let the end of the leg get before re-positioning it
--what the leg needs to point at:
local tx = GetValueNumber("tx", target[1])
local ty = GetValueNumber("ty", target[2])
local distance_to_target = distance(tx, ty, target[1], target[2])
--mix target position between ex, ey and tx, ty, based on time (picks up feet slightly when stepping):
local mult = smoothness_factor * (1+(2/3))
local inv = mult-time
local mix_x = (ex/mult)*time + (tx/mult)*inv
local mix_y = (ey/mult)*time + (ty/mult)*inv
--move feet toward target, unless already within range of it, in which case snap to the coordinates:
if distance_to_target > 10 then
target[1] = approx_rolling_average(target[1], mix_x, smoothness_factor)
target[2] = approx_rolling_average(target[2], mix_y, smoothness_factor)
elseif target[1] ~= tx and target[2] ~= ty then
target[1] = tx
target[2] = ty
--[a stepping noise could go here]
end
--if the leg stretches too far, find a new target:
if (distance(tx, ty, ex, ey) > total_leglength and distance(tx, ty, futurex, futurey) > total_leglength) or distance(tx, ty, ex, ey) == 0 then
local surfaces_found = false
--try to find nearby surfaces up to 3 times before resorting to stepping on air:
for i=1,3 do
potential_x, potential_y = (vel_x*60)+math.random(-total_leglength, total_leglength), (vel_y*60)+math.random(-total_leglength, total_leglength)
mag = math.sqrt(((ex+potential_x)-ex)^2 + ((ey+potential_y)-ey)^2)
potential_x, potential_y = ex + ((potential_x/mag)*total_leglength), ey + ((potential_y/mag)*total_leglength)
did_hit, hit_x, hit_y = RaytracePlatforms(ex, ey, potential_x, potential_y)
if did_hit then
tx = hit_x
ty = hit_y
surfaces_found = true
break
end
end
--step on air:
if not surfaces_found then
tx = potential_x
ty = potential_y
end
time = mult
end
--apply changes:
SetValueNumber("tx", tx)
SetValueNumber("ty", ty)
SetValueNumber("targ_x", target[1])
SetValueNumber("targ_y", target[2])
SetValueNumber("time", math.max(0,time-1))
To use it, name it "IK.lua" and put it in mods/yourmod/files/.
Then, save this xml script below as "ik_leg.xml", and put it in mods/yourmod/files/ as well.
<Entity name="ik_leg">
<InheritTransformComponent>
</InheritTransformComponent>
<Entity tags="IK">
<SpriteComponent
image_file="data/entities/animals/lukki/lukki_feet/lukki_limb_a_long.png"
z_index="1.1"
offset_x="0"
offset_y="5"
>
</SpriteComponent>
</Entity>
<Entity tags="knee">
<SpriteComponent
image_file="data/entities/animals/lukki/lukki_feet/lukki_knee.png"
z_index="1.09"
offset_x="5"
offset_y="5"
>
</SpriteComponent>
</Entity>
<Entity tags="IK">
<SpriteComponent
image_file="data/entities/animals/lukki/lukki_feet/lukki_limb_a_long.png"
z_index="1.1"
offset_x="0"
offset_y="5"
>
</SpriteComponent>
</Entity>
<Entity tags="knee">
<SpriteComponent
image_file="data/entities/animals/lukki/lukki_feet/lukki_knee.png"
z_index="1.09"
offset_x="5"
offset_y="5"
>
</SpriteComponent>
</Entity>
<Entity tags="IK">
<SpriteComponent
image_file="data/entities/animals/lukki/lukki_feet/lukki_limb_b_long.png"
z_index="1.1"
offset_x="0"
offset_y="5"
>
</SpriteComponent>
</Entity>
<LuaComponent
script_source_file="yourmod/files/scripts/IK.lua"
execute_every_n_frame="1"
remove_after_executed="0"
>
</LuaComponent>
<VariableStorageComponent
_tags="length"
value_int="38"
>
</VariableStorageComponent>
<VariableStorageComponent
_tags="IK_targ_x"
value_int="30"
>
</VariableStorageComponent>
<VariableStorageComponent
_tags="IK_targ_y"
value_int="-60"
>
</VariableStorageComponent>
</Entity>
By default this adds 3 limb segments to the leg. To add or remove segments, add or remove however many of these </Entity> child-block-things as you want (the parts of the xml that look like this):
<Entity>
<SpriteComponent
image_file="data/entities/animals/lukki/lukki_feet/lukki_limb_a.png"
z_index="1.1"
offset_x="0"
offset_y="5"
>
</SpriteComponent>
</Entity>
After you've saved the file, use Lua to spawn the ik_leg.xml entity, and parent it to whichever other entity you want the IK leg to stick out of, like this:
local x, y = EntityGetTransform(entity)
local ik_leg = EntityLoad("mods/yourmod/files/ik_leg.xml", x, y)
EntityAddChild(entity, ik_leg)
To set the coordinates you want the IK limb to point at, or to change the length between the joints, use Lua again to set ik_leg.xml's VariableStorageComponent values, like this:
local targcomp_x = EntityGetFirstComponent(entity, "VariableStorageComponent", "IK_targ_x")
local targcomp_y = EntityGetFirstComponent(entity, "VariableStorageComponent", "IK_targ_y")
local length_between_joints_component = EntityGetFirstComponent(entity, "VariableStorageComponent", "length")
ComponentSetValue2(targcomp_x, "value_int", whatever_number)
ComponentSetValue2(targcomp_x, "value_int", whatever_number)
ComponentSetValue2(length_between_joints_component, "value_int", whatever_number)
Lua Wall: A Wall that works entirely through lua[]
This script gives you a wall through lua. It requires you to set the width and height of the wall, and then it does the rest.
As the script is built, the wall works only for the player character.
dofile_once("data/scripts/lib/utilities.lua")
local entity_id = GetUpdatedEntityID()
local x, y = EntityGetTransform(entity_id)
local players = EntityGetWithTag("player_unit")
local character_data_component = EntityGetFirstComponentIncludingDisabled(players[1], "CharacterDataComponent")
local vel_x, vel_y = ComponentGetValue2( character_data_component, "mVelocity" )
if #players > 0 then
local player_x, player_y = EntityGetTransform(players[1])
local old_player_x = GetValueNumber("old_player_x", player_x)
local old_player_y = GetValueNumber("old_player_y", player_y)
SetValueNumber("old_player_x", player_x)
SetValueNumber("old_player_y", player_y)
new_x = player_x
new_y = player_y
local variable_storage = EntityGetComponent(entity_id, "VariableStorageComponent")
for k, v in pairs(variable_storage)do
local name = ComponentGetValue2(v, "name")
if(name == "top_edge")then
top_edge = ComponentGetValue2(v, "value_string")
elseif(name == "bottom_edge")then
bottom_edge = ComponentGetValue2(v, "value_string")
elseif(name == "left_edge")then
left_edge = ComponentGetValue2(v, "value_string")
elseif(name == "right_edge")then
right_edge = ComponentGetValue2(v, "value_string")
end
end
local old_top_edge = GetValueNumber("old_top_edge", y)
local old_bottom_edge = GetValueNumber("old_bottom_edge", y)
SetValueNumber("old_top_edge", y + top_edge)
SetValueNumber("old_bottom_edge", y + bottom_edge)
local old_left_edge = GetValueNumber("old_left_edge", x)
local old_right_edge = GetValueNumber("old_right_edge", x)
SetValueNumber("old_left_edge", x + left_edge)
SetValueNumber("old_right_edge", x + right_edge)
if ( old_player_y < y + top_edge or old_player_y > y + bottom_edge ) or ( old_top_edge > player_y ) or ( old_bottom_edge < player_y ) then
if ( player_y > y + top_edge and player_y < y ) and ( player_x > x + left_edge and player_x < x + right_edge ) then
new_y = y + top_edge - 1
ComponentSetValue2(character_data_component, "is_on_ground", true)
ComponentSetValue2(character_data_component, "mVelocity", vel_x, 0)
elseif ( player_y < y + bottom_edge and player_y > y ) and ( player_x > x + left_edge and player_x < x + right_edge ) then
new_y = y + bottom_edge + 2
ComponentSetValue2(character_data_component, "mVelocity", vel_x, 0)
end
-- elseif ( old_player_x < x + left_edge or old_player_x > x + right_edge ) or ( old_left_edge > player_x ) or ( old_right_edge < player_x ) then
else
if ( player_x > x + left_edge and player_x < x ) and ( player_y > y + top_edge and player_y < y + bottom_edge ) then
new_x = x + left_edge - 1
elseif ( player_x < x + right_edge and player_x > x ) and ( player_y > y + top_edge and player_y < y + bottom_edge ) then
new_x = x + right_edge + 1
end
end
if ( new_x ~= player_x ) or ( new_y ~= player_y ) then
EntitySetTransform(players[1], new_x, new_y)
--Inflict damage if squashing player:
function kill_player()
EntityInflictDamage(players[1], 999, "DAMAGE_PHYSICS_HIT", "", "NORMAL", 0, 0)
-- local damage_model_component = EntityGetFirstComponent(players[1], "DamageModelComponent")
-- ComponentSetValue2(damage_model_component, "hp", 0)
-- ComponentSetValue2(damage_model_component, "air_needed", true)
-- ComponentSetValue2(damage_model_component, "air_in_lungs", 0)
end
local ingrown_factor = 1
local player_left_width = ComponentGetValue2( character_data_component, "collision_aabb_min_x" )
local player_right_width = ComponentGetValue2( character_data_component, "collision_aabb_max_x" )
local player_upper_height = ComponentGetValue2( character_data_component, "collision_aabb_min_y" ) - 1
local player_lower_height = ComponentGetValue2( character_data_component, "collision_aabb_max_y" )
local check_left_edge, hit_x1, hit_y1 = RaytracePlatforms(player_x + player_left_width, player_y + player_upper_height + 1, player_x + player_left_width, player_y + player_lower_height - 1)
local check_right_edge, hit_x2, hit_y2 = RaytracePlatforms(player_x + player_right_width, player_y + player_upper_height + 1, player_x + player_right_width, player_y + player_lower_height -1 )
local check_upper_edge, hit_x3, hit_y3 = RaytracePlatforms(player_x + player_left_width + 1, player_y + player_upper_height, player_x + player_right_width - 1, player_y + player_upper_height )
local check_bottom_edge, hit_x4, hit_y4 = RaytracePlatforms(player_x + player_left_width + 1, player_y + player_lower_height, player_x + player_right_width - 1, player_y + player_lower_height )
--if ( ( old_player_x < x + left_edge or old_player_x > x + right_edge ) or ( old_left_edge > player_x ) or ( old_right_edge < player_x ) ) and ( check_left_edge or check_right_edge) then
if ( ( old_player_x < x + left_edge or old_player_x > x + right_edge ) or ( old_left_edge > player_x ) or ( old_right_edge < player_x ) ) and ( check_left_edge or check_right_edge) then
kill_player()
elseif ( ( old_player_y < y + top_edge ) or ( old_top_edge > player_y ) ) and check_upper_edge then
-- elseif ( ( old_player_y < y + top_edge or old_player_y > y + bottom_edge ) or ( old_top_edge > player_y ) or ( old_bottom_edge < player_y ) ) and ( check_upper_edge or check_bottom_edge) then
GamePrint("Squished Top")
kill_player()
elseif ( ( old_player_y > y + bottom_edge ) or ( old_bottom_edge < player_y ) ) and check_bottom_edge then
GamePrint("Squished Bottom")
kill_player()
end
end
end
Just run this every frame through a lua script on an entity and it will produce this malleable wall.
For this to work, the entity bearing the lua component needs to have a couple of variable storage components, defining the distance of edges from the center.
<Entity name="Lua Wall">
<SpriteComponent
image_file="data/props_gfx/temple_door.png"
offset_x="7"
offset_y="30" >
</SpriteComponent>
<VariableStorageComponent
name="top_edge"
value_string="-30"
></VariableStorageComponent>
<VariableStorageComponent
name="bottom_edge"
value_string="30"
></VariableStorageComponent>
<VariableStorageComponent
name="left_edge"
value_string="-7"
></VariableStorageComponent>
<VariableStorageComponent
name="right_edge"
value_string="7"
></VariableStorageComponent>
<LuaComponent
script_source_file="data/scripts/props/lua_wall.lua"
execute_every_n_frame="1"
>
</LuaComponent>
</Entity>
Storing values from a component and using them later[]
This tool will allow you to store values of a component in a variable storage component, make temporary changes to a component and then use insert the old values back later.
So first here's the lua script that stores the old values and sets the new values. In this case it's setting a bunch of values in the CharacterPlatformingComponent to 0.
dofile_once("data/scripts/lib/utilities.lua")
local entity_id = GetUpdatedEntityID()
local character_platforming_component = EntityGetFirstComponentIncludingDisabled(entity_id, "CharacterPlatformingComponent")
local properties_to_change = {
run_velocity = 0,
fly_speed_max_up = 0,
jump_velocity_y = 0,
jump_velocity_x = 0,
fly_velocity_x = 0,
fly_speed_max_up = 0
}
if character_platforming_component then
local old_values = ""
for k, v in pairs(properties_to_change) do
local value = ComponentGetValue2(character_platforming_component, k)
local string_value = tostring(value)
if type(value) == "boolean" then
string_value = value and "1" or "0"
end
old_values = old_values .. k .. ":" .. type(value) .. "=" .. string_value
if next(properties_to_change, k) then
old_values = old_values .. ","
end
ComponentSetValue2(character_platforming_component, k, v)
end
EntityAddComponent2(entity_id, "VariableStorageComponent", {
name = "old_values",
value_string = old_values,
})
end
Here's the script which would read out the old values from the variable storage component and which would set them back to what they were before on the entity:
dofile_once("data/scripts/lib/utilities.lua")
function split_string(inputstr, sep)
sep = sep or "%s"
local t= {}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end
function toboolean(str)
return str == "1"
end
local entity_id = GetUpdatedEntityID()
local character_platforming_component = EntityGetFirstComponentIncludingDisabled(entity_id , "CharacterPlatformingComponent")
local var_store = get_variable_storage_component(entity_id, "old_values")
local stored_values = ComponentGetValue2(var_store, "value_string")
if stored_values then
local properties = split_string(stored_values, ",")
for i, v in ipairs(properties) do
local name, prop_type, value = string.match(v, "(.+):(.+)=(.+)")
value = _G["to"..prop_type](value)
ComponentSetValue2(character_platforming_component, name, value)
end
EntityRemoveComponent(entity_id, var_store)
end
And there you go. This will allow you to make temporary changes to a component for a temporary time, and then apply the old values back once you're done.
And that's about it, hopefully this all made sense, will update soon so it makes even more sense :D