OSP Kit Axe Trap

Currently Viewing (Users: 0, Guests: 1)

Dalion

Squire
Best answers
0
Another creative use of Native resources.

Warning: to implement this OSP, coding skills aren't enough, you should also have at least basic scene editor experience. If you don't, hiner's tutorial is a great starting point.

Any part of this code could use improvements. You are free to improve this or to use as it is, just don't forget to give credits.

How does it work? The trap consists of a large hanging double-sided axe, which has two modes - hidden and moving. In a moving mode, it will move from left to right non-stop, dealing damage to anything on its way. In a hidden mode, it's static and harmless, simple as that. You can switch between these modes by pressing Num_Enter anytime, without cooldown. Trap doesn't have collision so it won't interrupt agent movement. You can place as many axes as you want, on any distance from each other, you may turn them in opposite sides to create "scissors" traps etc, lots of creative uses for this one.


Now let's get to the implementation itself. There are 4 steps:

1) First of all, we need axe mesh. Native does have a double-sided axe (dblhead_ax in weapon_meshes1.brf), but it's so blurry and low-quality that we'd rather make a twin bardiche. You know, to have less "just a giant weapon" vibes and more badass look overall. To make this happen, we'll need OpenBRF tool.
- navigate to Warband's main directory (where the .exe is) and find a folder named CommonRes
- find a weapons_e.brf file in it and use OpenBRF to open it
- search for two_handed_battle_axe_d mesh, that's the bardiche we need. Right mouse click on it -> Duplicate, then duplicate the copy so we have original and 2 clones
- right mouse click on either of duplicates -> Mirror
- select them both holding Ctrl -> Right mouse click on them -> Combine meshes. This can be performed only if both meshes use the same material (our case exactly), otherwise we'd have to rename them to the same name, to make game treat them as 1 mesh, but keeping them separate in brf
- rename our combined mesh to "guilliotine"
- right mouse click on it -> Roto-translate-rescale...
Here we should turn it the specific way so it's hanging down, and the coords starting point should be at the top of the handle. In order to see the starting point, make sure you have the Floor checkbox enabled in the middle of program window. I was able to achieve the needed position by moving Z axis on 0.25, and rotating X and Z axis on -90. Also increase it's scale by 4x. Normally we don't know the needed scale when we are planning the mesh, it is seen only in a scene editor later, so it took me some tests (and will take you couple as well if you decide do something similar on your own), but I found the optimal size and can now spare you the trouble:

When your mesh looks the same as mine, save the changes and open module system (doesn't really matter where you would save it, you can cut it to your module's Resource folder and list in module.ini alongside other resources, or just save where it is, weapons_e.brf is already listed in Native).
If you are too lazy to go through all these steps (and chances are I guessed right), you can download already adjusted and game-ready mesh here

2) Open module_scene_props.py, and add our prop:

Code:
("guilliotine", 0, "guilliotine", "0", [

    (ti_on_scene_prop_init,[  # this will put our trap in a hidden mode when mission starts. I highly recommend commenting it out when modifying the scene,
                              # and activating only when all is ready and placed on its places.

    (store_trigger_param_1, ":instance_no"),
    (prop_instance_get_position, pos4, ":instance_no"),    
    (position_move_z, pos4, 320),
    (prop_instance_set_position, ":instance_no", pos4),
    ]),

    (ti_on_scene_prop_is_animating,[  # this is the damage dealing part. Triggers only while axe is moving

    (store_trigger_param_1, ":instance_no"),
    (set_fixed_point_multiplier, 100),
    (prop_instance_get_position, pos3, ":instance_no"),
    (position_move_z, pos3, -540),
   #(particle_system_burst, "psys_torch_fire", pos3, 1), # I used this particle to determine the position of damage dealing, but you can go crazy with your own
                                                         # particles here! Trap looks much more spectacular when it's glowing or burning or whatever...
    (try_for_agents, ":agent", pos3, 50),
        (agent_is_alive, ":agent"),
        (agent_deliver_damage_to_agent, ":agent", ":agent", 70, "itm_bardiche"), # when adjusting the damage keep in mind it is dealt every frame of the animation
        (agent_play_sound, ":agent", "snd_metal_hit_low_armor_high_damage"),
        (particle_system_burst, "psys_game_blood_2", pos3),
    (try_end),
    ]),

    (ti_on_scene_prop_animation_finished,[  # this one is responsible for most animations. When adjusting, keep in mind that second X rotation must be
                                            # two times bigger than first one (like 90 is x2 of 45)
    (store_trigger_param_1, ":instance_no"),
    (prop_instance_get_position, pos2, ":instance_no"),
    (try_begin),
        (scene_prop_slot_eq, ":instance_no", scene_prop_open_or_close_slot, 1), # start of animation cycle, moving right
        (position_rotate_x, pos2, 45),
        (prop_instance_animate_to_position, ":instance_no", pos2, 50),
        (scene_prop_set_slot, ":instance_no", scene_prop_open_or_close_slot, 2),
        (prop_instance_play_sound, ":instance_no", "snd_draw_axe", sf_vol_15),
    (else_try),
        (scene_prop_slot_eq, ":instance_no", scene_prop_open_or_close_slot, 2), # middle of animation cycle, moving sideways
        (position_get_rotation_around_x, ":x", pos2),
        (eq, ":x", 45),
        (position_rotate_x, pos2, -90),
        (prop_instance_animate_to_position, ":instance_no", pos2, 100),
        (prop_instance_play_sound, ":instance_no", "snd_axe_pass_by", sf_vol_15),
    (else_try),
        (scene_prop_slot_eq, ":instance_no", scene_prop_open_or_close_slot, 2),
        (position_get_rotation_around_x, ":x", pos2),
        (neq, ":x", 45),
        (position_rotate_x, pos2, 90),
        (prop_instance_animate_to_position, ":instance_no", pos2, 100),
        (prop_instance_play_sound, ":instance_no", "snd_axe_pass_by", sf_vol_15),
    (else_try),
        (scene_prop_slot_eq, ":instance_no", scene_prop_open_or_close_slot, 3), # end of animation cycle, hiding the trap
        (position_move_z, pos2, 320),
        (prop_instance_animate_to_position, ":instance_no", pos2, 50),
        (scene_prop_set_slot, ":instance_no", scene_prop_open_or_close_slot, 0),
        (prop_instance_play_sound, ":instance_no", "snd_draw_greatsword", sf_vol_15),
    (try_end),
    ]),
]),
3) Add this mission trigger at the top of module_mission_templates.py and later on add its name on any needed template:

Code:
axe_trap_control = (0, 0, 0,
    [(key_clicked, key_numpad_enter),],
    [
    (try_for_prop_instances, ":axe", "spr_guilliotine"),
        (try_begin),
            (scene_prop_slot_eq, ":axe", scene_prop_open_or_close_slot, 0), # if the trap is hidden - activate
            (prop_instance_get_position, pos2, ":axe"),        
            (position_move_z, pos2, -320),
            (prop_instance_animate_to_position, ":axe", pos2, 50),
            (scene_prop_set_slot, ":axe", scene_prop_open_or_close_slot, 1),
            (prop_instance_play_sound, ":axe", "snd_draw_greatsword", sf_vol_15),        
        (else_try),                                                         # if it is active - hide
            (prop_instance_get_starting_position, pos1, ":axe"),
            (prop_instance_animate_to_position, ":axe", pos1, 50),
            (scene_prop_set_slot, ":axe", scene_prop_open_or_close_slot, 3),
            (prop_instance_play_sound, ":axe", "snd_draw_greataxe", sf_vol_15),
        (try_end),
    (try_end),
    ])
Example for ai_training mission, the one that is called when entering the siege scenes via cheats


4) Now we are set, and ready to put the trap on our scene in-game. Open scene editor, find spr_guilliotine and let your imagination do the rest (but make sure your axes are hanging on full length, since ti_on_scene_prop_init trigger will move them upwards at the scene start):

On the screenshot you can't really tell, but my axes are turned on 180 compared to each other, so they will move in opposite directions. You can also hang them on spr_belfry_wheel_old with scale [0.03, 0.3, 0.3] for more immersion (avoid using common spr_belfry_wheel since it is participating in siege tower scripts), and use 2 instances of spr_siege_ladder_8m as a "ruler" that will help you to place axes on equal distance from each other.

That's all. I highly recommend to restart the game client after you finished your scene changes, in order to avoid possible "white metal" glitch and other unpleasant surprises.

Happy modding!

 
Last edited:

Antonis

Marquis
WBWF&SVCNW
Best answers
0
I agree with all those that came before me. :shifty:
Great stuff AND a very clear guide on how to implement the whole thing.
 

Dogmeat

Recruit
Best answers
0
yep great, imaginative and wonderful documentation
an exemplar of the modding spirit sir/maam - salut
was going to post something humble im bumbling around on getting some non human head and bodies going and you've set the standard, thanks
 

AndyYa

Regular
M&BWB
Best answers
0
Great job!
Another way to use it - is to run trap by the trigger.

download modified brf - link

Code:
axe_trap_control = (0.1, 0, 0,
    [(assign, ":go", 0),
     (scene_prop_get_num_instances, ":num_guilliotine_triggers", "spr_guilliotine_trigger"),
     (try_for_range, ":trigger_no", 0, ":num_guilliotine_triggers"),
        (scene_prop_get_instance, ":guilliotine_trigger_prop_id", "spr_guilliotine_trigger", ":trigger_no"),
        (try_for_agents, ":agent"),
            (agent_is_alive, ":agent"),
            (scene_prop_has_agent_on_it, ":guilliotine_trigger_prop_id", ":agent"),
            (try_begin),
                (ge, "$g_trap_axe", 0),
            (else_try),
                (lt, "$g_trap_axe", 0),
                (assign, ":go", 1),
                (assign, "$g_trap_axe", ":agent"),
            (try_end),
        (try_end),
        (try_begin),
            (ge, "$g_trap_axe", 0),
            (neg|scene_prop_has_agent_on_it, ":guilliotine_trigger_prop_id", ":agent"),
            (assign, ":go", 1),
            (assign, "$g_trap_axe", -1),
        (try_end),
      (try_end),
     (eq, ":go", 1),
    ],
    [(try_for_prop_instances, ":axe", "spr_guilliotine"),
        (try_begin),
            (scene_prop_slot_eq, ":axe", scene_prop_open_or_close_slot, 0), # if the trap is hidden - activate
            (prop_instance_get_position, pos2, ":axe"),
            (position_move_z, pos2, -320),
            (prop_instance_animate_to_position, ":axe", pos2, 50),
            (scene_prop_set_slot, ":axe", scene_prop_open_or_close_slot, 1),
            (prop_instance_play_sound, ":axe", "snd_draw_greatsword", sf_vol_15),
        (else_try),                                                         # if it is active - hide
            (prop_instance_get_starting_position, pos1, ":axe"),
            (prop_instance_animate_to_position, ":axe", pos1, 50),
            (scene_prop_set_slot, ":axe", scene_prop_open_or_close_slot, 3),
            (prop_instance_play_sound, ":axe", "snd_draw_greataxe", sf_vol_15),
        (try_end),
     (try_end),
    ])
new scene_props
("guilliotine_trigger",0,"guilliotine_trigger","bo_guilliotine_trigger", []), #sokf_invisible - hiding trap trigger
 
Last edited: