Page 2 of 3 FirstFirst 123 LastLast
Results 11 to 20 of 26

Thread: Multiplayer health via script

  1. #11

    Default

    There are several ways to achieve what you want. Some of them are:

    • create your own health items via tik file
    • create your own health items via script
    • use triggers




    1. tik file:
    Code:
    TIKI
    setup
    {
    	scale 0.52
    	path models/items/healthpacks
    	skelmodel healthcanteen.skd
    	surface material1 shader healthcanteen
    }
    
    init
    {
    	server
    	{
    		classname Health
    		amount 5
    		dmamount 5
    		pickupsound med_canteen
    	}
    }
    
    animations
    {
    	idle healthcanteen.skc
    }
    
    /*QUAKED item_health_5 (0 0 0) (0 0 0) (0 0 0)
    Gives 5 Health when picked up
    */
    I just copied the canteen (25hp) file and changed all '25' values to '5'. You could then create two more for '10' and '15' hp. You could even mess around with its size or shader (texture) to give them each a slightly different appearance.

    2. health item via script:

    So, with that file above fresh in your mind, you can use that as a reference to create a health item via script using the same properties you see there inside the 'server' block. I suppose you could see tikis as an extension of the script. You can also look at the 'Item' class in Game Module Classes to see the properties you can set. For example, you see the 'set_respawn' property on the link I provided (scroll down a little bit)? That would help you if you want it to be a 'one off', as the item won't respawn after being used.

    Anyway, here is the script:

    local.health = spawn Health // this is the 'classname' shown in the tik
    local.health.model = "models/items/item_25_healthbox.tik" // this is the model to use (you could use any of the health pack models here, or use your own)
    local.health amount 5
    local.health dmamount 5
    local.health pickupsound med_canteen // self explanatory, the sound it makes when someone picks it up
    local.health origin (20 20 20) // insert your own location here
    local.health set_respawn 0 // this will set the respawn property as discussed above


    Notice how I've used local.health.model = instead of local.health model, I did this because the latter didn't work. That's usually my rule of thumb. If one way doesn't work, try the other. There are also some properties that can be set both ways, by calling the function (origin) or just setting the property directly (.origin =). In this case it's up to you how you want to do it. The parentheses (brackets) you see on Game Module Classes are optional, and only used if you're calling the function, or when setting vectors (in this case, an origin). I personally don't use them.

    3. using triggers:
    On your various trigger_use in Radiant, you can set a property we can use in the script to tell us how much the player will heal by. So, for example, we have a trigger_use with a 'setthread' of 'Heal' and a '#hp' of '5' (make sure to use the hash for custom properties).

    Then all you need is a single function that ALL of your trigger_use's can call. You can give each trigger a different '#hp' value and it will work fine.


    Heal:
    local.player = parm.other

    // we don't want the player to make this healing spot disappear if they already have full health
    if (local.player.health == local.player.max_health) end

    local.trigger = self
    local.hp = float local.trigger.hp //grab the '#hp' property '5' we set on the trigger in Radiant and make it a 'float' (turns it into 5.0). This is needed because floats are used for 'heal' below

    // divide '5.0' by 100 to get '0.05', which is the value needed for 'heal' below
    local.health = (local.hp / 100)

    // do the heal
    local.player heal local.health

    // remove the trigger as this is a 'one off'
    local.trigger remove
    end


    Bear in mind the value '5' or '5.0' are just for this example of 5hp. The other triggers will pass in whatever '#hp' value you give them and it will work. I also assumed you wanted the trigger/items to only give health to one player lucky enough to use it. If no, let me know how you want it to work e.g. as many times as the player 'uses' the trigger, or only once per player etc...

    Hope that helps!
    Last edited by 1337Smithy; February 11th, 2020 at 02:29 AM.

  2. #12

    Default

    1337Smithy: This will help me start to understand how the modules / classes are used. Thank you for your time and efforts.

    1. via tik file:
    Dang, why didn't I think of this?

    2. Game Module Classes:
    Yeah I tried with / without brackets, with / without space, with / without period but I was also at a loss for the rest of that line.
    For example: (don't laugh)
    local.player.health (tried both with and without the period, "local.player health") but what was to follow that?
    a) +5
    b) "+5"
    c) (+5)
    d) = local.player.health +5

    Of course none of these worked and it stopped my entire script.
    Trust me when I say it wasn't for a lack of trying and changing more than one thing at a time makes it near impossible to pin-point any problem.
    I also tried "heal' and "give health" with each of those suffix I listed.

    3. use triggers:
    Mate, this is right on the money for what I would like to have in my map's script.
    But I do appreciate you giving three alternative methods.

    Thank you for taking the time to hold my hand and guide me through this.
    The linked page needs examples set out like you have done. Just one line of how each class could be written.
    That's how I learned VB from the Microsoft pages that had examples.


    I also appreciate how you've made both this and the increments health script flexible, one thread for all related triggers of the same type. This makes things so much easier and the script much neater.
    Whatever they're paying you here should be doubled. lol

    Just checked the currency exchange rate:
    AUS$1 = 0.52 Pound sterling

    I think you're better off taking a few $ notes from the Monopoly set. lol

    Cheers mate.

  3. #13

    Default

    Sorry mate, I forgot to answer this.

    Quote Originally Posted by 1337Smithy View Post
    I also assumed you wanted the trigger/items to only give health to one player lucky enough to use it. If no, let me know how you want it to work e.g. as many times as the player 'uses' the trigger, or only once per player etc...

    Hope that helps!
    I'm still undecided. I'd like to extend game-play by including health but not make it too easy (a.k.a God Mode).
    Besides it gets them to make use of all the little Easter eggs I've included.
    I was initially "thinking" (that's dangerous for the local community and more work for you lol) to have one use per player or a timed cool-off period between that player's heals.
    Last edited by AccadaccA; February 11th, 2020 at 05:14 PM.

  4. #14

    Default

    Quote Originally Posted by AccadaccA View Post
    local.player.health (tried both with and without the period, "local.player health") but what was to follow that?
    a) +5
    b) "+5"
    c) (+5)
    d) = local.player.health +5
    Adding onto the player's existing health means you need to get what they already have. This can be done a couple of ways:

    // local.player.health will become the value already stored in local.player.health plus 5
    local.player.health = local.player.health + 5



    // does the same as above but is nicer
    local.player.health += 5


    Then via command:

    local.player health (local.player.health + 5)

    The above wouldn't work for you though, as setting the 'health' of an entity ALSO sets the 'max_health'. This means that if you set the health to '50' the max_health will also become '50', meaning the health bar on the HUD will be full, because they have 100% of their health (50 out of 50).

    That's why they also included a 'healthonly' command:

    local.player healthonly (local.player.health + 5)

    That one does for you. 'heal', the one you were using, is nice because it does the 'local.player.health + 5' calculation for you in the engine, but 'healthonly' is better because it appears to provide more accurate results and you don't need to convert the value (dividing by 100) before passing it in.

    Quote Originally Posted by AccadaccA View Post
    I was initially "thinking" (that's dangerous for the local community and more work for you lol) to have one use per player or a timed cool-off period between that player's heals.
    You can code it as to give yourself a choice of timed, per session, or single use.

    It gets a little trickier if you want to restrict triggers for specific players, as you need some method of storing those restrictions, preferably on the players themselves...

    ...which is what I went with .

    Now, you'll notice how 'local.' has been changed to 'group.' in some instances, and this means that those variables will be accessible from all the threads we are calling, meaning we don't need to worry about manually passing data between the threads.

    Also, the triggers in Radiant now have extra properties. setthread will be the same ('Heal' in this example), but now it will come with a '$mode' and '#time' on top of the existing '#hp'. There are 3 modes: 'session' (only once per player per map), 'timed' (has a 'recharge' time before it can be used by the same player again) and 'single' (used once and then removed). Note dollar ($) symbol before 'mode', this is because we are passing in a string. $ for strings and # for integers. '#time' is number of seconds e.g. key: #time value: 10 will be ten seconds. If your $mode is set to 'time' and you don't include a #time then it defaults to 30 seconds.


    Heal:
    group.player = parm.other
    group.trigger = self

    // we don't want the player to use healing spot if they already have full health
    // or if they are restricted (temporarily or otherwise)
    if (waitthread Restricted || group.player.health == group.player.max_health) end

    local.hp = group.trigger.hp // grab the hp property
    local.mode = group.trigger.mode // grab the mode
    local.time = group.trigger.time // grab the time

    // do the heal
    group.player healthonly (group.player.health + group.trigger.hp)

    // play sound (comment this out if you don't want health sound, or include your own)
    group.trigger playsound med_kit

    switch (local.mode)
    {
    // if mode is session or timed then restrict this healing spot for this player
    case "session":
    case "timed":
    thread RestrictHealingSpot; break
    case "single":
    default:
    group.trigger remove; break // remove the trigger as this is a single use trigger
    }
    end

    RestrictHealingSpot:

    if !(group.player.restricted_healing_spots)
    {
    // this is the first healing spot to be restricted so make index start at 1
    group.player.restricted_healing_spots[1] = group.trigger
    }
    else
    {
    // grab number of existing healing spot restrictions
    local.existing_restrictions = group.player.restricted_healing_spots.size

    // grab the position at which the new restriction will reside
    local.new_restriction_pos = local.existing_restrictions + 1

    // now set the restriction by placing the restricted healing trigger inside this new array position
    group.player.restricted_healing_spots[local.new_restriction_pos] = group.trigger
    }

    if (group.trigger.mode == "timed")
    {
    // if there is no time property, then default will be 30 seconds
    if !(group.trigger.time) group.trigger.time = 30

    // start the timer
    thread RechargeTimer
    }
    end

    RechargeTimer:
    // set the time that the player can use this again
    local.recharge_time = level.time + group.trigger.time

    // wait until we have reached the rechard time
    while (group.player && local.recharge_time > level.time)
    {
    wait 1
    }

    // if player is still around, then remove their restriction from this healing spot
    if (group.player)
    {
    thread RemoveRestriction
    }
    end

    RemoveRestriction:
    // grab number of existing restrictions
    local.restrictions = group.player.restricted_healing_spots.size
    // grab the restricted healing spot
    local.healing_spot = group.trigger

    // loop over all the restrictions
    for (local.i = 1; local.i <= local.restrictions; local.i++)
    {
    // if we have found the restriction we want to remove
    if (group.player.restricted_healing_spots[local.i] == local.healing_spot)
    {
    // if there are any restrictions to the right in the array we want to move them all down to fill in this empty space
    if (group.player.restricted_healing_spots[local.i + 1])
    {
    // this will update the array without the targeted restriction (restriction thus removed)
    thread ResortRestrictionArray local.i
    }
    else
    {
    // there aren't anymore restrictions to the right in the array, so just remove it here
    group.player.restricted_healing_spots[local.i] = NIL
    }
    break // we found and sorted the restriction, so leave the for loop
    }
    }
    end

    ResortRestrictionArray local.index:
    // grab the old array we want to change
    local.old_array = group.player.restricted_healing_spots
    // use a temporary 'new' array that we can use to write over the old array
    local.new_array[1] = NIL
    // this is the indexing the new array will use (starting at 1)
    local.j = 1
    // loop over the old array
    for (local.i = 1; local.i <= local.old_array.size; local.i++)
    {
    // add all the restrictions to the new array EXCEPT for the restriction we want to remove
    if (local.i != local.index)
    {
    local.new_array[local.j] = local.old_array[local.i]
    local.j++
    }
    }
    // now write over the old array, meaning only valid restrictions are remaining
    group.player.restricted_healing_spots = local.new_array
    end

    // what this does is return either '1' or '0' (true or false)
    Restricted:
    local.healing_spots = group.player.restricted_healing_spots.size
    if (local.healing_spots > 0)
    {
    for (local.i = 1; local.i <= local.healing_spots; local.i++)
    {
    if (group.trigger == group.player.restricted_healing_spots[local.i])
    {
    end 1 // return true as this healing spot for this player is currently restricted
    }
    }
    }
    end 0 // return false as this healing spot for this player is not restricted


    You can use any type of trigger with this code, not just trigger_use.

    I tested this myself, but you'll probably want to keep an eye out for any bugs I've missed, especially when multiple players are in a server.

    I've tried to include useful comments. If you have any queries, let me know.
    Last edited by 1337Smithy; February 13th, 2020 at 02:19 AM.

  5. #15

    Default

    Quote Originally Posted by 1337Smithy View Post
    Adding onto the player's existing health means you need to get what they already have. This can be done a couple of ways:

    // local.player.health will become the value already stored in local.player.health plus 5
    local.player.health = local.player.health + 5
    Hey, I got something right with my example "d)".
    I must have had something else wrong in that thread for it not to work.
    Then again, I didn't have a space between the "+" and the "5".

    Quote Originally Posted by 1337Smithy View Post
    I tested this myself, but you'll probably want to keep an eye out for any bugs I've missed, especially when multiple players are in a server.
    I'm in the same boat mate, not knowing how things will perform on multi-player servers and I no longer play the game so I'm not familiar with any server hosts to ask.
    I test as a solo player in multiplayer mode and Shadow can no longer test with Noiz until they find a server for map testing since AAAA dropped their servers.


    Once again you've gone above and beyond.
    Thank you very much for not only me but anyone else who happens to read this in search for the answer.

  6. #16

    Default

    No problem! Let me know if it works OK when you get a chance to test it .

  7. #17

    Default

    Sorry but I've noticed something that I've often thought about and it's now prompted to ask.

    The calling of threads from within a thread.
    I have thought to use "thread threadname" but have always gone with "GoTo threadname"
    I know the "thread" call is used in the "main" thread but with the exception of it there, I only recall seeing "Goto" in common threads of tutorial maps.

    Is there a time where you should use one and not the other or isn't there a difference?

  8. #18

    Default

    Generally speaking, people are against goto statements, but specifically to move to another function? Not very often at all. What 'goto' will do is move the current thread to that label, and take everything with it (all your local variables for instance), which isn't really that desirable a lot of the time. The rest of your function won't be executed either. Here's an example:


    TestThread1:

    local.var1 = "hey"
    local.var2 = "bye"

    goto TestThread2

    if (local.var1)
    {
    // do stuff
    }

    end

    TestThread2:

    if (local.var1)
    {
    //do stuff
    }
    end


    Now, the thread executing the TestThread1 code will jump to TestThread2 and thus will not continue to the if statement in TestThread1, but it will recognise local.var1 in TestThread2 as it's the same thread. Remember, 'local.' variables are only accessible within the thread that creates them. But moving from function to function defeats the purpose of the 'local.' scope.

    What 'thread label' does is create a NEW thread that starts at that label, and the previous thread will continue happily with any following instructions.

    It's funny, people call the functions 'threads' but they aren't. Threads are the instances that executes your code starting at the label you specify in your script, and will carry on doing so until it's told to terminate (usually an 'end' statement). If you were to remove all your 'ends' the thread that is created automatically when a map loads at the start (usually from 'main') of your script will continue until the bottom of your script.

    So, for what you're talking about, no, goto is rarely used, and I personally wouldn't do it.

  9. #19

    Default

    Gotcha. Cheers.

    Although I have used "Goto" in my threads to call the "heal" thread mid-stream, so to speak to then include "wait" times until the trigger is made triggerable again.
    I'm calling your "heal" thread from several other threads that include different sounds, functions and wait periods.
    So far it all works well but I will change "goto" to "thread" just to keep the MOH Gods happy. lol

    Is it okay for me to send you the map for you to take a look over everything?
    I'll give it a quick compile (2 minutes) and a test first. I bet I find one flaw after another between each compile and don't end up sending it for days. lol

  10. #20

    Default

    Sure, I can take a look. It may be the case that 'thread' isn't the solution you need (e.g. if you're using goto instead of a loop) so it may be worth me checking over it to see what it's doing .

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •