diff --git a/autogen/autogen.sh b/autogen/autogen.sh
old mode 100644
new mode 100755
diff --git a/autogen/convert_structs.py b/autogen/convert_structs.py
index 222f90704..8cea014bf 100644
--- a/autogen/convert_structs.py
+++ b/autogen/convert_structs.py
@@ -68,6 +68,7 @@ override_field_immutable = {
"TextureInfo": [ "*" ],
"Object": ["oSyncID", "createdThroughNetwork"],
"GlobalObjectAnimations": [ "*"],
+ "SpawnParticlesInfo": [ "model" ],
}
sLuaManuallyDefinedStructs = [
diff --git a/autogen/lua_constants/built-in.lua b/autogen/lua_constants/built-in.lua
index c485e5403..138470c04 100644
--- a/autogen/lua_constants/built-in.lua
+++ b/autogen/lua_constants/built-in.lua
@@ -81,11 +81,12 @@ function vec3f_mul(dest, a)
end
function vec3f_normalize(dest)
- local invsqrt = 1.0 / math.sqrt(dest.x * dest.x + dest.y * dest.y + dest.z * dest.z)
- if invsqrt == 0 then
+ local divisor = math.sqrt(dest.x * dest.x + dest.y * dest.y + dest.z * dest.z)
+ if divisor == 0 then
return dest
end
+ local invsqrt = 1.0 / divisor
dest.x = dest.x * invsqrt
dest.y = dest.y * invsqrt
dest.z = dest.z * invsqrt
diff --git a/docs/lua/functions.md b/docs/lua/functions.md
index 686283974..5f5f79684 100644
--- a/docs/lua/functions.md
+++ b/docs/lua/functions.md
@@ -631,6 +631,7 @@
- [obj_get_next_with_same_behavior_id](#obj_get_next_with_same_behavior_id)
- [obj_get_next_with_same_behavior_id_and_field_f32](#obj_get_next_with_same_behavior_id_and_field_f32)
- [obj_get_next_with_same_behavior_id_and_field_s32](#obj_get_next_with_same_behavior_id_and_field_s32)
+ - [obj_get_temp_spawn_particles_info](#obj_get_temp_spawn_particles_info)
- [obj_has_behavior_id](#obj_has_behavior_id)
- [obj_has_model_extended](#obj_has_model_extended)
- [obj_set_model_extended](#obj_set_model_extended)
@@ -11595,6 +11596,26 @@ The `reliable` field will ensure that the packet arrives, but should be used spa
+## [obj_get_temp_spawn_particles_info](#obj_get_temp_spawn_particles_info)
+
+### Lua Example
+`local SpawnParticlesInfoValue = obj_get_temp_spawn_particles_info(modelId)`
+
+### Parameters
+| Field | Type |
+| ----- | ---- |
+| modelId | [enum ModelExtendedId](constants.md#enum-ModelExtendedId) |
+
+### Returns
+[SpawnParticlesInfo](structs.md#SpawnParticlesInfo)
+
+### C Prototype
+`struct SpawnParticlesInfo* obj_get_temp_spawn_particles_info(enum ModelExtendedId modelId);`
+
+[:arrow_up_small:](#)
+
+
+
## [obj_has_behavior_id](#obj_has_behavior_id)
### Lua Example
diff --git a/docs/lua/structs.md b/docs/lua/structs.md
index 07936a68a..d12abfa24 100644
--- a/docs/lua/structs.md
+++ b/docs/lua/structs.md
@@ -1655,7 +1655,7 @@
| forwardVelBase | `integer` | |
| forwardVelRange | `integer` | |
| gravity | `integer` | |
-| model | `integer` | |
+| model | `integer` | read-only |
| offsetY | `integer` | |
| sizeBase | `number` | |
| sizeRange | `number` | |
diff --git a/mods/football.lua b/mods/football.lua
index 37579581c..84e6db971 100644
--- a/mods/football.lua
+++ b/mods/football.lua
@@ -163,6 +163,48 @@ define_custom_obj_fields({
oFrozen = 'u32',
})
+function bhv_ball_particle_trail(obj)
+ local spi = obj_get_temp_spawn_particles_info(E_MODEL_SPARKLES)
+ if spi == nil then
+ return nil
+ end
+
+ spi.behParam = 2
+ spi.count = 1
+ spi.offsetY = -1 * ballRadius
+ spi.forwardVelBase = 8
+ spi.forwardVelRange = 0
+ spi.velYBase = 6
+ spi.velYRange = 0
+ spi.gravity = 0
+ spi.dragStrength = 5
+ spi.sizeBase = 10
+ spi.sizeRange = 30
+
+ cur_obj_spawn_particles(spi)
+end
+
+function bhv_ball_particle_bounce(obj)
+ local spi = obj_get_temp_spawn_particles_info(E_MODEL_MIST)
+ if spi == nil then
+ return nil
+ end
+
+ spi.behParam = 3
+ spi.count = 5
+ spi.offsetY = -1 * ballRadius
+ spi.forwardVelBase = 6
+ spi.forwardVelRange = -6
+ spi.velYBase = 6
+ spi.velYRange = -6
+ spi.gravity = 0
+ spi.dragStrength = 5
+ spi.sizeBase = 8
+ spi.sizeRange = 13
+
+ cur_obj_spawn_particles(spi)
+end
+
function bhv_ball_init(obj)
-- flags and such
obj.oFlags = OBJ_FLAG_UPDATE_GFX_POS_AND_ANGLE
@@ -245,7 +287,9 @@ function bhv_ball_player_collision(obj)
local vDifference = { x = objPoint.x - cylPoint.x, y = objPoint.y - cylPoint.y, z = objPoint.z - cylPoint.z }
local differenceDir = { x = vDifference.x, y = vDifference.y, z = vDifference.z }
- vec3f_normalize(differenceDir)
+ if vec3f_length(differenceDir) ~= 0 then
+ vec3f_normalize(differenceDir)
+ end
alterPos.x = (cylPoint.x + differenceDir.x * (playerBallRadius + playerRadius + 1)) - objPoint.x
alterPos.y = (cylPoint.y + differenceDir.y * (playerBallRadius + playerRadius + 1)) - objPoint.y
@@ -312,6 +356,11 @@ function bhv_ball_player_collision(obj)
end
end
+ -- make sure player has a velocity
+ if not doReflection and vPlayerMag == 0 then
+ doReflection = true
+ end
+
--------------------------------------
-- calculate velocity (interaction) --
--------------------------------------
@@ -456,8 +505,10 @@ function bhv_ball_loop(obj)
-- detect normal along movement vector
local vMag = vec3f_length(v)
- local vNorm = { x = v.x / vMag, y = v.y / vMag, z = v.z / vMag }
- b = { x = v.x + vNorm.x * (vMag + ballRadius), y = v.y + vNorm.y * (vMag + ballRadius), z = v.z + vNorm.z * (vMag + ballRadius) }
+ if vMag > 0 then
+ local vNorm = { x = v.x / vMag, y = v.y / vMag, z = v.z / vMag }
+ b = { x = v.x + vNorm.x * (vMag + ballRadius), y = v.y + vNorm.y * (vMag + ballRadius), z = v.z + vNorm.z * (vMag + ballRadius) }
+ end
info = collision_find_surface_on_ray(
a.x, a.y, a.z,
@@ -467,6 +518,7 @@ function bhv_ball_loop(obj)
local colNormals = {}
if info.surface ~= nil then
table.insert(colNormals, { x = info.surface.normal.x, y = info.surface.normal.y, z = info.surface.normal.z })
+ bhv_ball_particle_bounce(obj)
else
table.insert(colNormals, nil)
end
@@ -573,6 +625,16 @@ function bhv_ball_loop(obj)
network_send_object(obj, false)
end
+ -- spawn a particle trail
+ if vMag > 50 then
+ bhv_ball_particle_trail(obj)
+ end
+
+ -- hack: make sure we never set velocity to nan
+ if obj.oVelX ~= obj.oVelX then obj.oVelX = 0 end
+ if obj.oVelY ~= obj.oVelY then obj.oVelY = 0 end
+ if obj.oVelZ ~= obj.oVelZ then obj.oVelZ = 0 end
+
-- hack: save pos/vel to detect packets
cb.oGlobalOwner = obj.oGlobalOwner
cb.oHitTime = obj.oHitTime
diff --git a/src/game/behaviors/tree_particles.inc.c b/src/game/behaviors/tree_particles.inc.c
index ad82b1f17..c2f69a30c 100644
--- a/src/game/behaviors/tree_particles.inc.c
+++ b/src/game/behaviors/tree_particles.inc.c
@@ -14,7 +14,7 @@ void bhv_tree_snow_or_leaf_loop(void) {
obj_mark_for_deletion(o);
if (o->oTimer > 100)
obj_mark_for_deletion(o);
- if (gPrevFrameObjectCount > 212)
+ if (gPrevFrameObjectCount > (OBJECT_POOL_CAPACITY * 212 / 240))
obj_mark_for_deletion(o);
o->oFaceAnglePitch += o->oAngleVelPitch;
o->oFaceAngleRoll += o->oAngleVelRoll;
diff --git a/src/game/object_helpers.c b/src/game/object_helpers.c
index abe9fa9e6..b82780c21 100644
--- a/src/game/object_helpers.c
+++ b/src/game/object_helpers.c
@@ -2238,13 +2238,14 @@ void cur_obj_spawn_particles(struct SpawnParticlesInfo *info) {
s32 numParticles = info->count;
// If there are a lot of objects already, limit the number of particles
- if (gPrevFrameObjectCount > 150 && numParticles > 10) {
+ if (gPrevFrameObjectCount > (OBJECT_POOL_CAPACITY * 150 / 240) && numParticles > 10) {
numParticles = 10;
}
+
// We're close to running out of object slots, so don't spawn particles at
// all
- if (gPrevFrameObjectCount > 210) {
+ if (gPrevFrameObjectCount > (OBJECT_POOL_CAPACITY * 210 / 240)) {
numParticles = 0;
}
diff --git a/src/pc/lua/smlua_cobject_autogen.c b/src/pc/lua/smlua_cobject_autogen.c
index efb2a77b0..91569ed65 100644
--- a/src/pc/lua/smlua_cobject_autogen.c
+++ b/src/pc/lua/smlua_cobject_autogen.c
@@ -1439,7 +1439,7 @@ static struct LuaObjectField sSpawnParticlesInfoFields[LUA_SPAWN_PARTICLES_INFO_
{ "forwardVelBase", LVT_S8, offsetof(struct SpawnParticlesInfo, forwardVelBase), false, LOT_NONE },
{ "forwardVelRange", LVT_S8, offsetof(struct SpawnParticlesInfo, forwardVelRange), false, LOT_NONE },
{ "gravity", LVT_S8, offsetof(struct SpawnParticlesInfo, gravity), false, LOT_NONE },
- { "model", LVT_U8, offsetof(struct SpawnParticlesInfo, model), false, LOT_NONE },
+ { "model", LVT_U8, offsetof(struct SpawnParticlesInfo, model), true, LOT_NONE },
{ "offsetY", LVT_S8, offsetof(struct SpawnParticlesInfo, offsetY), false, LOT_NONE },
{ "sizeBase", LVT_F32, offsetof(struct SpawnParticlesInfo, sizeBase), false, LOT_NONE },
{ "sizeRange", LVT_F32, offsetof(struct SpawnParticlesInfo, sizeRange), false, LOT_NONE },
diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c
index 53b8c22ce..6247f31e0 100644
--- a/src/pc/lua/smlua_constants_autogen.c
+++ b/src/pc/lua/smlua_constants_autogen.c
@@ -73,10 +73,11 @@ char gSmluaConstants[] = ""
" return dest\n"
"end\n"
"function vec3f_normalize(dest)\n"
-" local invsqrt = 1.0 / math.sqrt(dest.x * dest.x + dest.y * dest.y + dest.z * dest.z)\n"
-" if invsqrt == 0 then\n"
+" local divisor = math.sqrt(dest.x * dest.x + dest.y * dest.y + dest.z * dest.z)\n"
+" if divisor == 0 then\n"
" return dest\n"
" end\n"
+" local invsqrt = 1.0 / divisor\n"
" dest.x = dest.x * invsqrt\n"
" dest.y = dest.y * invsqrt\n"
" dest.z = dest.z * invsqrt\n"
diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c
index 276c4b262..3ade1085c 100644
--- a/src/pc/lua/smlua_functions_autogen.c
+++ b/src/pc/lua/smlua_functions_autogen.c
@@ -7397,6 +7397,17 @@ int smlua_func_obj_get_next_with_same_behavior_id_and_field_s32(lua_State* L) {
return 1;
}
+int smlua_func_obj_get_temp_spawn_particles_info(lua_State* L) {
+ if(!smlua_functions_valid_param_count(L, 1)) { return 0; }
+
+ int modelId = smlua_to_integer(L, 1);
+ if (!gSmLuaConvertSuccess) { return 0; }
+
+ smlua_push_object(L, LOT_SPAWNPARTICLESINFO, obj_get_temp_spawn_particles_info(modelId));
+
+ return 1;
+}
+
int smlua_func_obj_has_behavior_id(lua_State* L) {
if(!smlua_functions_valid_param_count(L, 2)) { return 0; }
@@ -8519,6 +8530,7 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "obj_get_next_with_same_behavior_id", smlua_func_obj_get_next_with_same_behavior_id);
smlua_bind_function(L, "obj_get_next_with_same_behavior_id_and_field_f32", smlua_func_obj_get_next_with_same_behavior_id_and_field_f32);
smlua_bind_function(L, "obj_get_next_with_same_behavior_id_and_field_s32", smlua_func_obj_get_next_with_same_behavior_id_and_field_s32);
+ smlua_bind_function(L, "obj_get_temp_spawn_particles_info", smlua_func_obj_get_temp_spawn_particles_info);
smlua_bind_function(L, "obj_has_behavior_id", smlua_func_obj_has_behavior_id);
smlua_bind_function(L, "obj_has_model_extended", smlua_func_obj_has_model_extended);
smlua_bind_function(L, "obj_set_model_extended", smlua_func_obj_set_model_extended);
diff --git a/src/pc/lua/smlua_obj_utils.c b/src/pc/lua/smlua_obj_utils.c
index c570a1368..dd46cdabe 100644
--- a/src/pc/lua/smlua_obj_utils.c
+++ b/src/pc/lua/smlua_obj_utils.c
@@ -190,3 +190,16 @@ struct Object *obj_get_next_with_same_behavior_id_and_field_f32(struct Object *o
}
return NULL;
}
+
+struct SpawnParticlesInfo* obj_get_temp_spawn_particles_info(enum ModelExtendedId modelId) {
+ static struct SpawnParticlesInfo spi = { 0 };
+
+ u8 loadedModelId = smlua_model_util_load(modelId);
+ if (loadedModelId == 0xFF) {
+ LOG_ERROR("failed to load model %u", modelId);
+ return NULL;
+ }
+ spi.model = loadedModelId;
+
+ return &spi;
+}
diff --git a/src/pc/lua/smlua_obj_utils.h b/src/pc/lua/smlua_obj_utils.h
index 494d7aa71..44e2405a1 100644
--- a/src/pc/lua/smlua_obj_utils.h
+++ b/src/pc/lua/smlua_obj_utils.h
@@ -28,4 +28,8 @@ struct Object *obj_get_next_with_same_behavior_id(struct Object *o);
struct Object *obj_get_next_with_same_behavior_id_and_field_s32(struct Object *o, s32 fieldIndex, s32 value);
struct Object *obj_get_next_with_same_behavior_id_and_field_f32(struct Object *o, s32 fieldIndex, f32 value);
+// misc obj helpers
+
+struct SpawnParticlesInfo* obj_get_temp_spawn_particles_info(enum ModelExtendedId modelId);
+
#endif
diff --git a/src/pc/network/discord/user.c b/src/pc/network/discord/user.c
index de06da1f1..bd49c4270 100644
--- a/src/pc/network/discord/user.c
+++ b/src/pc/network/discord/user.c
@@ -5,8 +5,10 @@
static void on_current_user_update(UNUSED void* data) {
LOGFILE_INFO(LFT_DISCORD, "> on_current_user_update");
- struct DiscordUser user;
+ struct DiscordUser user = { 0 };
app.users->get_current_user(app.users, &user);
+
+ // remember user id
app.userId = user.id;
gPcDebug.id = user.id;