OSP Code Combat Soulfire Arrows w/ DOT [WSE]

Users who are viewing this thread

Yagababa

Recruit
Hey everyone,

I'm proud to announce my first OSP! There's a lot of flaming arrow projects on the Forge, but I wasn't really satisfied w/ the results I was getting--so I went and made a new one.

I've created a solid system in WFaS with the new WSE build (shoutout @K700) that combines a couple particle systems and triggers to create a clean aesthetic, and a damage over time system that works great in MP (haven't tested in SP but it should work w/ very few alterations).

Before I get started, I should mention that while this will also work in Warband, it does require WSE and a reasonable knowledge of the module system. Let's get to it!

First things first, we're gonna need some particle systems, so chuck these in module_particle_systems:
Python:
################################################################################
# Soul arrows
################################################################################
    # Soul arrows hit
    ("soul_fire_hit", psf_billboard_3d|psf_global_emit_dir|psf_always_emit|psf_randomize_size|psf_randomize_rotation, "prt_mesh_fire_1",
     50, 0.2, 0.2, 0.03, 10.0, 0.0,     #num_particles, life, damping, gravity_strength, turbulance_size, turbulance_strength
     (0.5, 0., (1, 0),        #alpha keys
     (0.5, 0.6), (1, 0.9),      #red keys
     (0.5, 0.6),(1, 0.9),       #green keys
     (0.5, 0.9), (1, 0.9),      #blue keys
     (0, 0.15),   (0.6, 0.5),   #scale keys
     (0.06, 0.06, 0.06),      #emit box size
     (0, 0, 0.,               #emit velocity
     0.5,                       #emit dir randomness
     200,                       #rotation speed
     0.5                        #rotation damping
    ),

    # Soul arrow smoldering
    ("soul_smolder_smoke", psf_billboard_3d|psf_global_emit_dir|psf_always_emit, "prtcl_dust_a",
     20, 0.5, 0.2, -0.2, 10.0, 0.5,     #num_particles, life, damping, gravity_strength, turbulance_size, turbulance_strength
     (0.5, 0.25), (1, 0),       #alpha keys
     (0.0, 0.2), (1, 0.1),      #red keys
     (0.0, 0.2),(1, 0.09),      #green keys
     (0.0, 0.2), (1, 0.0,     #blue keys
     (0, 0.5),   (0.8, 2.5),    #scale keys
     (0.1, 0.1, 0.1),           #emit box size
     (0, 0, 1.5),               #emit velocity
     0.1                        #emit dir randomness
    ),
    ("soul_smolder_sparks", psf_billboard_3d|psf_global_emit_dir|psf_always_emit|psf_randomize_size,  "prt_sparks_mesh_1",
     30, 1.5, 0.2, 0, 10.0, 0.2,     #num_particles, life, damping, gravity_strength, turbulance_size, turbulance_strength
     (0.66, 1), (1, 0),          #alpha keys
     (0.1, 0.3), (1, 0.7),      #red keys
     (0.1, 0.1),(1, 0.7),       #green keys
     (0.1, 0.7), (1, 0.9),      #blue keys
     (0.1, 0.05),   (1, 0.05),  #scale keys
     (0.1, 0.1, 0.1),           #emit box size
     (0, 0, 0.9),               #emit velocity
     0.0,                       #emit dir randomness
     0,
     0,
    ),

    # Soul arrow draw
    ("soul_draw_fire", psf_billboard_3d|psf_global_emit_dir|psf_always_emit|psf_randomize_size|psf_randomize_rotation, "prt_mesh_fire_1",
     30, 0.1, 0.4, 0.03, 10.0, 0.3,     #num_particles, life, damping, gravity_strength, turbulance_size, turbulance_strength
     (0.5, 0., (1, 0),        #alpha keys
     (0.5, 0.5), (1, 0.9),      #red keys
     (0.5, 0.5),(1, 0.9),       #green keys
     (0.5, 0.9), (1, 0.9),      #blue keys
     (0, 0.15),   (0.3, 0.2),   #scale keys
     (0.04, 0.04, 0.01),      #emit box size
     (0, 0, 0.,               #emit velocity
     0.0,                       #emit dir randomness
     200,                       #rotation speed
     0.5                        #rotation damping
    ),
    ("soul_draw_smoke", psf_billboard_3d|psf_global_emit_dir|psf_always_emit, "prtcl_dust_a",
     1, 0.3, 0.2, -0.2, 10.0, 0.5,     #num_particles, life, damping, gravity_strength, turbulance_size, turbulance_strength
     (0.2, 0.1), (0.6, 0),       #alpha keys
     (0.0, 0.2), (1, 0.1),      #red keys
     (0.0, 0.2),(1, 0.09),      #green keys
     (0.0, 0.2), (1, 0.0,     #blue keys
     (0, 0.5),   (0.8, 2.5),    #scale keys
     (0.1, 0.1, 0.1),           #emit box size
     (0, 0, 1.5),               #emit velocity
     0.1                        #emit dir randomness
    ),
    ("soul_draw_sparks", psf_billboard_3d|psf_global_emit_dir|psf_always_emit|psf_randomize_size,  "prt_sparks_mesh_1",
     1, 0.3, 0.2, 0, 10.0, 0.2,     #num_particles, life, damping, gravity_strength, turbulance_size, turbulance_strength
     (0.3, 0.5), (0.6, 0),          #alpha keys
     (0.1, 0.3), (1, 0.7),      #red keys
     (0.1, 0.1),(1, 0.7),       #green keys
     (0.1, 0.7), (1, 0.9),      #blue keys
     (0.1, 0.05),   (1, 0.05),  #scale keys
     (0.1, 0.1, 0.1),           #emit box size
     (0, 0, 0.9),               #emit velocity
     0.0,                       #emit dir randomness
     0,
     0,
    ),

    # Soul arrow flying
    ("soul_missile_smoke", psf_billboard_3d|psf_global_emit_dir|psf_always_emit, "prtcl_dust_a",
     50, 0.8, 0.2, -0.2, 10.0, 0.1,     #num_particles, life, damping, gravity_strength, turbulance_size, turbulance_strength
     (0.8, 0.5), (1, 0),       #alpha keys
     (0.0, 0.2), (1, 0.1),      #red keys
     (0.0, 0.2),(1, 0.09),      #green keys
     (0.0, 0.2), (1, 0.0,     #blue keys
     (0.1, 1),   (1.5, 3),    #scale keys
     (0.1, 0.1, 0.1),           #emit box size
     (0, 0, 1.5),               #emit velocity
     0.1                        #emit dir randomness
    ),
    ("soul_missile_sparks", psf_billboard_3d|psf_global_emit_dir|psf_always_emit|psf_randomize_size,  "prt_sparks_mesh_1",
     30, 0.8, 0.2, 0, 10.0, 0.05,     #num_particles, life, damping, gravity_strength, turbulance_size, turbulance_strength
     (0.66, 1), (1, 0),          #alpha keys
     (0.1, 0.3), (1, 0.7),      #red keys
     (0.1, 0.1),(1, 0.7),       #green keys
     (0.1, 0.7), (1, 0.9),      #blue keys
     (1, 0.1),   (1.5, 0.15),  #scale keys
     (0.15, 0.15, 0.15),           #emit box size
     (0, 0, 0.9),               #emit velocity
     0.0,                       #emit dir randomness
     0,
     0,
    ),

Next, you're going to want to create a new arrow item in module_items, with a few triggers for our new particle effects. Be sure to declare your meshes, I just threw in some placeholders here--any native meshes will do.

Side note: I also don't have any imodbits because of my current project, but feel free to add/modify any of the base item stats to your liking.

Also, while we're here, go ahead and create a dummy weapon called FLAME_KILL, and make it a type of weapon you don't use anywhere in your mod. I don't use throwing stones, so this is perfect for me. I'll explain why in a bit:
Python:
["soulfire_arrows", "Soulfire Arrows", [("YOUR_MESH_HERE",0),("flying_missile", ixmesh_flying_ammo),("YOUR_CARRY_MESH_HERE", ixmesh_carry),("YOUR_QUIVER_MESH_HERE", ixmesh_inventory)], itp_type_arrows|itp_default_ammo, itcf_carry_quiver_back, 100, weight(3)|weapon_length(95)|thrust_damage(3, pierce)|max_ammo(30), 0,
[(ti_on_init_missile, [
    (particle_system_add_new, "psys_soul_missile_smoke"),
    (particle_system_add_new, "psys_soul_missile_sparks"),
]),
(ti_on_init_item, [
    (set_position_delta, 0, 100, 0),
    (particle_system_add_new, "psys_soul_smolder_smoke"),
    (particle_system_add_new, "psys_soul_smolder_sparks"),
    (set_current_color,150, 130, 70),
    (add_point_light, 10, 30),
]),
(ti_on_missile_hit,
[
    (particle_system_burst, "psys_soul_fire_hit", pos1, 15),
    (set_current_color, 150, 130, 70),
    (add_point_light, 10, 30),
]),
]],

["FLAME_KILL", "FLAME_KILL", [("bullet",0)], itp_type_thrown|itp_primary, itcf_throw_stone, 0, weight(0)|difficulty(0)|spd_rtng(0)|shoot_speed(0)|thrust_damage(0, blunt)|max_ammo(0)|weapon_length(0), 0],

Great, now let's add a couple of new agent slots in module_constants; you may have to change the numbers if these slots are already being used. In my case, 30+ were free:
Python:
slot_agent_on_fire = 30
slot_agent_fire_timer = 31
slot_agent_fire_bone = 32
slot_agent_fire_igniter = 33

slot_agent_wielding_soul_fire_arrows = 34

Okay last step, onto the triggers. This is where the bulk of the magic happens. We're going to add six new triggers before the mission templates. If you already have a ti_on_agent_hit and/or ti_on_item_wielded check, you can just add the contents of these in your existing trigger. Or just add these new ones, whatever you're feeling.

Important to note here is that there's two places where it checks whether an agent is using a bow--I check this using is_between, and having all my bows in module_items appear consecutively. I recommend doing the same, and replacing BOWS_BEGIN w/ the first bow to appear and BOWS_END as one item after the last. You can also accomplish this by using constants, but for me this was simpler:
Python:
# Make sure freshly spawned agents aren't on fire
reset_status_on_spawn = (ti_on_agent_spawn, 0, 0, [],
[
     (store_trigger_param_1, ":agent_no"),
     (agent_set_slot, ":agent_no", slot_agent_on_fire, 0),
])
# Apply fire status effect on hit
hit_checks = (ti_on_agent_hit, 0, 0, [(multiplayer_is_server),],
[
    (store_trigger_param_1, ":agent_no"),
    (store_trigger_param_2, ":attacker_no"),
    (store_trigger_param_3, ":damage"),

    (try_begin),
        (is_between, reg0, "BOWS_BEGIN", "BOWS_END"), # Check if agent is using a bow
        (assign, ":soul_arrows_check", 0),
        (try_for_range, ":cur_slot", 0, 3), # Check if agent has soulfire arrows
            (agent_get_item_slot, ":item_to_check", ":attacker_no", ":cur_slot"),
            (eq, ":item_to_check", "itm_soulfire_arrows"),
            (assign, ":soul_arrows_check", 1),
        (try_end),
        (try_begin),
            (eq, ":soul_arrows_check", 1), # If so, add the status effect via the new slots
            (agent_set_slot, ":agent_no", slot_agent_on_fire, 1),
            (agent_set_slot, ":agent_no", slot_agent_fire_timer, 12), # Set timer to 12, you could make this random or adjust as you see fit
            (agent_set_slot, ":agent_no", slot_agent_fire_igniter, ":attacker_no"),

            (store_trigger_param, ":hit_bone", 4), # Store ignited 'bone' that was hit w/ arrow for smoldering particle effects
            (agent_set_slot, ":agent_no", slot_agent_fire_bone, ":hit_bone"),
        (try_end),
    (try_end),
])

# Check for actively wielded flaming arrows
wielding_checks = (ti_on_item_wielded, 0, 0, [],[
(store_trigger_param_1, ":agent_no"),
(store_trigger_param_2, ":item_no"),
(try_begin),
    (is_between, ":item_no", "BOWS_BEGIN", "BOWS_END"), # Check if agent is using a bow
    (assign, ":soul_arrows_check", 0),
    (try_for_range, ":cur_slot", 0, 3), # Check if agent has soulfire arrows
        (agent_get_item_slot, ":item_to_check", ":agent_no", ":cur_slot"),
        (eq, ":item_to_check", "itm_soulfire_arrows"),
        (assign, ":soul_arrows_check", 1),
    (try_end),
    (try_begin),
        (eq, ":soul_arrows_check", 1),
        (agent_set_slot, ":agent_no", slot_agent_wielding_soul_fire_arrows, 1),
    (else_try),
        (agent_set_slot, ":agent_no", slot_agent_wielding_soul_fire_arrows, 0),
    (try_end),
(else_try),
    (agent_set_slot, ":agent_no", slot_agent_wielding_soul_fire_arrows, 0),
(try_end),
])

# Apply fire damage
fire_damage_check = (0.25, 0, 0, [(multiplayer_is_server)],
[
    (try_for_agents, ":cur_agent"),
        (agent_get_slot, "n_fire", ":cur_agent", slot_agent_on_fire), # Check if agent is on fire
        (eq, "n_fire", 1),
        (agent_get_slot, ":fire_timer", ":cur_agent", slot_agent_fire_timer),
        (try_begin),
            (gt, ":fire_timer", 0),
            (val_sub, ":fire_timer", 1), # Tick down fire timer
            (agent_set_slot, ":cur_agent", slot_agent_fire_timer, ":fire_timer"),
            (store_agent_hit_points, ":agent_health", ":cur_agent", 1),
            (val_sub, ":agent_health", 1),
            (agent_set_hit_points, ":cur_agent", ":agent_health", 1), # Take away 1 HP from fire damage, feel free to change this value for balancing
            (try_begin), # Check if we should kill the agent due to fire damage
                (lt, ":agent_health", 2),
                (agent_get_slot, ":igniter", ":cur_agent", slot_agent_fire_igniter),
                (agent_deliver_damage_to_agent, ":igniter", ":cur_agent", 10, "itm_FLAME_KILL"), # Kill the agent using the dummy flame weapon, and give credit to the igniter
            (try_end),
        (else_try),
            (agent_set_slot, ":cur_agent", slot_agent_on_fire, 0), # If the fire goes out, reset the slot status
        (try_end),
    (try_end),
])

# Add smoldering particle effect to burning agents
smolder = (0.25, 0, 0, [], [
(try_for_agents, ":cur_agent"),
    (agent_is_alive, ":cur_agent"),
    (agent_is_active, ":cur_agent"),
    (agent_get_slot, ":on_fire", ":cur_agent", slot_agent_on_fire),
    (eq, ":on_fire", 1),
    (agent_get_slot, ":bone_no", ":cur_agent", slot_agent_fire_bone), # Using bone from ti_on_agent_hit
    (agent_get_bone_position, pos2, ":cur_agent", ":bone_no", 1),
    (particle_system_burst, "psys_soul_smolder_smoke", pos2, 40),
    (particle_system_burst, "psys_soul_smolder_sparks", pos2, 40),
(try_end),
])

# Add flame particles to drawn bow
draw_flame = (0, 0, 0, [], [
(try_for_agents, ":cur_agent"),
    (agent_get_slot, ":fire_arrow_check", ":cur_agent", slot_agent_wielding_soul_fire_arrows),
    (eq, ":fire_arrow_check", 1),
    (try_begin),
        (agent_get_animation, ":animation", ":cur_agent", 1),
        (eq, ":animation", "anim_ready_bow"),
        # (this_or_next|game_key_is_down, gk_attack),
        # (key_is_down, key_left_mouse_button),

        (agent_get_bone_position, pos2, ":cur_agent", hb_item_l, 1),
        (position_move_z, pos2, 10, 1),
        (particle_system_burst, "psys_soul_draw_fire", pos2, 5), # total num particles
        (particle_system_burst, "psys_soul_draw_smoke", pos2, 2),
        (particle_system_burst, "psys_soul_draw_sparks", pos2, 2),
        (set_current_color,150, 130, 70),
        (add_point_light, 10, 30),
    (try_end),
(try_end),
])

Make sure to call these:
Python:
reset_status_on_spawn,
fire_damage_check,
hit_checks,
wielding_checks,
smolder,
draw_flame,
in the appropriate mission templates.

Notice when to kill an agent via fire damage, I apply it using the FLAME_KILL dummy weapon:
Python:
(agent_deliver_damage_to_agent, ":igniter", ":cur_agent", 10, "itm_FLAME_KILL"),

Now you can replace the appropriate kill icon in mp_icon_fight.dds to a flame. If you used a throwing rock like me, here's an example I made. Feel free to use it.

...Okay I lied--there's one more step, but it's completely optional.

In module scripts, find the script "game_get_item_extra_text", and add this to the try block:
Python:
...
(else_try),
     (eq, ":item_no", "itm_soulfire_arrows"),
     (try_begin),
          (eq, ":extra_text_id", 0),
          (set_result_string, "@Fire Damage"), # Your flavor text here
          (set_trigger_result, 0x7FC9FF), # Color of the text
     (try_end),
(else_try),
...

I should also mention it would be fairly simple to adapt this code to make normal, non-blue flaming arrows by editing the color keys in the particle systems.

And that should be just about everything! let me know if you have any questions or are having trouble getting this to work.

10/5/2021: Attached smoldering effect to ignited agent's hit bone instead of calculating offset; this is simpler, more optimized, and looks way better.
10/6/2021: Flame particles on draw now triggered by animation rather than game_key for better multiplayer compatibility, per Veledentella's suggestion
 
Last edited:
Thank you for your contribution, but it is mandatory that you put all scripts in code brackets, so that they are legible; they must not be formatted just like any other text. I cannot even bother to read them in this state. Shorter snippets may be put in inline code brackets, and longer ones in ordinary Python brackets. This is a minor thing when compared to the content of this thread, so do not be discouraged after all and keep being productive.
 

Yagababa

Recruit
Thank you for your contribution, but it is mandatory that you put all scripts in code brackets, so that they are legible; they must not be formatted just like any other text. I cannot even bother to read them in this state. Shorter snippets may be put in inline code brackets, and longer ones in ordinary Python brackets. This is a minor thing when compared to the content of this thread, so do not be discouraged after all and keep being productive.
Got it, thanks.
 
Thanks again. Flame missiles are somehow a hot topic and I can attest to that since I have extensively used them in both MP (NeoGK) and SP (Fire Arrow mod for M&B). Your set of scripts seems to be very elaborate and well-executed. I have one question though: ti_on_missile_hit bursts particles upon a surface hit, ti_on_init_missile initializes particles when a missile is flying, but what does ti_on_init_item do? Does it initialize particles when a missile is drawn?
 

Yagababa

Recruit
Thanks again. Flame missiles are somehow a hot topic and I can attest to that since I have extensively used them in both MP (NeoGK) and SP (Fire Arrow mod for M&B). Your set of scripts seems to be very elaborate and well-executed. I have one question though: ti_on_missile_hit bursts particles upon a surface hit, ti_on_init_missile initializes particles when a missile is flying, but what does ti_on_init_item do? Does it initialize particles when a missile is drawn?
Thanks! I'm still working on a couple details, but am happy with how it's turned out. I'm still relatively new to MB modding and learned a lot about triggers and positions while working on this--looking forward to trying some more ambitious projects in the future.

ti_on_init_item triggers when an arrow misses and gets stuck to the ground/a scene prop, I just added a little bit of smoke and sparks but no flame.

I couldn't find a trigger for an arrow being drawn (I tried a lot of them), so that's what the grossly circuitous slot/trigger system w/ draw_flame and wielding_checks is for.
 
As far as I know, missiles set on fire while being drawn are already scripted, so I will inquire how to do that.

 

Yagababa

Recruit
As far as I know, missiles set on fire while being drawn are already scripted, so I will inquire how to do that.

Please lmk if you find a trigger to serve that function, it would help optimization greatly (and also be useful in my next project)!
 
Please lmk if you find a trigger to serve that function, it would help optimization greatly (and also be useful in my next project)!
There is no trigger for that, but SupaNinjaMan notes that he would work it around like this: "I would do a try for agents, check if the are in a draw animation and also using flaming arrows, and if so, make a particle burst at the right hand item bone position with some offset along the y to account for the arrow length."
 

Yagababa

Recruit
There is no trigger for that, but SupaNinjaMan notes that he would work it around like this: "I would do a try for agents, check if the are in a draw animation and also using flaming arrows, and if so, make a particle burst at the right hand item bone position with some offset along the y to account for the arrow length."
Bummer--that's effectively what I ended up doing.
 
Top Bottom