ID:1353015
 
(See the best response by SuperSaiyanGokuX.)
Code:
        crowdcontrolAdd(mob/Me, mob/Target, cc, time, timeReductionPercent)
// if(cc in Me.LISTcrowdcontrol)
// Me.LISTcrowdcontrol.Remove(cc)
if(ismob(Target))
Me.crowdcontrolRemove(Me, Target)
Me.LISTcrowdcontrol.Insert(1, cc)
var/Reduction = (1/100)*timeReductionPercent
sleep(time - Reduction)
Me.LISTcrowdcontrol.Remove(cc)
world<<"off"


Problem description:

Hello, I have a problem and I can't find out better idea. I want to add CC system with stuns and similar things and it would work like:
Player A stuns Player B for 3 seconds.
after 1,5 second
Player C stuns Player B for 4 seconds.
And right here the first stun should have stop working in 1,5 seconds but when player C stuns his stun is "main" one and the first one is removed.
My only idea is making a list and I have a problem with sleep proc because after 1st stun time (don't know how to put it in words) after that 3 seconds stuns it removes stun unit so, 4sec stun is being canceled before time. Please, help me :)

3 sec stun
after 1,5sec
4 sec stun <- main stun, no stacking stuns
after 4 sec
= free to go

Best response
So, how are you handling your status effects? (like being stunned) Are you using toggled vars (as in "stun=0" or "stun=1")?

Another way (especially to handle situations where the same effect might be applied more than once) is to use a list to contain the target's status effects, and you add and remove those effects from the list (as text strings)

With this method, there's no need to worry about stacking, you just add and remove the status strings independent of each other (it's fine to add another "copy" of the status even while the first one is active; the one with the longest duration will remain as the "main" entry)

For example:

mob
var/StatusEffects[0]
proc
StunMe(mob/source,duration)
StatusEffects+="stun"
spawn(duration)
StatusEffects-="stun"
Move()
if("stun" in StatusEffects) return
..()


Of course, you'd need to adopt this system to your existing procs and such.

What happens is that, based on your example:

Player A stuns Player B for 3 seconds.
after 1,5 second
Player C stuns Player B for 4 seconds.
(Player A's stun still has 1,5 seconds to go, but that won't matter, just let it go!)
after 1,5 second (total of 3 seconds)
The "stun" added by Player A is removed, but the "stun" added by Player C is still in the list.
after 2,5 seconds
Player C's "stun" is removed, Player B is no longer stunned!
The easiest way to do it, would be to check if world.time is greater than the time this particular one was supposed to be removed.

Mob.stunrelease = world.time+time-Reduction


The easiest way would be to use world.time and set a variable, honestly. If you have to use objects, though, it's best not to have effects that cannot stack stay in the list the entire time, but rather override the existing object.

You can also use world.time to ensure that the time requirements have been met, and that there is not another stun pending.
Sorry Ter13 but I don't really understand what you just written (no offence, I'm just trying to adapt to that kind of English :D) Either way, thank you both! My sleep wasn't good option because as I said, it has been removing next stun because sleep from last call woke up ;D
Use world.timeofday, it's way more accurate than world.time

Basically what Ter means is that instead of saying "the effect lasts for a minute, remove it after that time", you instead say "if it's 00:00 right now, the effect will be gone in 00:01". To check if the effect is still active, you simply check if the current world.timeofday is before or after the time the effect wears off.
Wow now it sounds so easy ;D Thank you, I will check it.
In response to MisterPerson
It's less accurate. If the day changes and the time resets, your code is going to bug up.
In response to Ter13
One up from this is to actually note the number of ticks that something will last, and then the tick at which this began. This allows you to save and load status effects between sessions by just resetting the "began" time to the current world tick upon logging back in after loading.

If the only thing you keep track of is the release tick, you're going to have problems when you need to restart the session.
In response to FIREking
FIREking wrote:
One up from this is to actually note the number of ticks that something will last, and then the tick at which this began. This allows you to save and load status effects between sessions by just resetting the "began" time to the current world tick upon logging back in after loading.

If the only thing you keep track of is the release tick, you're going to have problems when you need to restart the session.

Combat states shouldn't be saved between sessions.

Mobs should not be released from the world during an active combat state.

Even if you were going to allow someone to log out during an active combat state, you should be accounting for world.time changes by subtracting the current world.time from the release delta and adding it back on load.

As for timeofday... It may be more accurate, but at midnight, it's going to gum your code up and you need to account for that.
In response to Ter13
You are confusing status effect for combat state.

Status effect, to me, is being "poisoned" or something similar. In these cases, you can have effects that could last hours or days.

I don't see why anyone needs the resolution of timeofday when you can't react between ticks anyway. World.time is just fine for the granularity.

How would your "accounting for world.time changes by subtracting current world.time from release delta and adding it back on load" work exactly? Show an example where people log out before a world is rebooted (and they are saved at this point) and then login after the world has started a new session since.
In response to SuperSaiyanGokuX
Hey I have one question, what will happen if I try to remove status effect before it ending with your method SuperSaiyanGokuX? And can I do that with only
 function(mob/source, cc)
source.LIST -= cc?

and what if I have other kind of status effects? Does that code overwrite it everytime I +=? (I want to use one list for all cc's)
In response to Paffci0
Paffci0 wrote:
Hey I have one question, what will happen if I try to remove status effect before it ending with your method SuperSaiyanGokuX? And can I do that with only
 function(mob/source, cc)
> source.LIST -= cc
>


If you remove the effect before it is supposed to end, the program will just quietly attempt to remove it from the list when the time comes, not find it, and do nothing.

That would be the general function to do so, yes.

You can also set up a function that will remove all of a specific entry, if you want to end that status completely, no matter how many sources have added it.

 
function_removecompletely(mob/source, cc)
for(cc in source.LIST)
source.LIST -= cc


and what if I have other kind of status effects? Does that code overwrite it everytime I +=? (I want to use one list for all cc's)

No, the += and -= operators when used with a list work the same as LIST.Add() and LIST.Remove(), they only add or remove the value. The rest of the list remains the same.
Ok thanks, I knew that += meant the same as LIST.Add() but I have completely forgot about it, tough day. Either way thank you and sorry. It works as I tested now. What if I did it with variables and connected it with spawn proc?
I mean something like that
src.stunned = 0
spawn(duration) src.stunned = 1
src<<"you will get stunned in [duration]"

and what if I want to remove it (stun) before spawn procs? Can I do it or it's better way to do it?

I had a big trouble with it long time ago but don't know if I gave you good example, if not I will update it tommorow.
In response to SuperSaiyanGokuX
SuperSaiyanGokuX wrote:
Paffci0 wrote:
Hey I have one question, what will happen if I try to remove status effect before it ending with your method SuperSaiyanGokuX? And can I do that with only
 function(mob/source, cc)
> > source.LIST -= cc
> >

If you remove the effect before it is supposed to end, the program will just quietly attempt to remove it from the list when the time comes, not find it, and do nothing.

That would be the general function to do so, yes.

You can also set up a function that will remove all of a specific entry, if you want to end that status completely, no matter how many sources have added it.

> function_removecompletely(mob/source, cc)
> for(cc in source.LIST)
> source.LIST -= cc
>

and what if I have other kind of status effects? Does that code overwrite it everytime I +=? (I want to use one list for all cc's)

No, the += and -= operators when used with a list work the same as LIST.Add() and LIST.Remove(), they only add or remove the value. The rest of the list remains the same.

Actually, SSGX, your system is going to be a bit slower than the below:

proc
removeAll(var/list/l,var/ref)
. = 1
while(.)
. = l.Remove(ref)


Remove won't return anything if it fails to remove something from the list, so we don't need to iterate through the list at all. We just need to check for the return value of remove.
In response to Ter13
Paffci0 wrote:
Ok thanks, I knew that += meant the same as LIST.Add() but I have completely forgot about it, tough day. Either way thank you and sorry. It works as I tested now.

No problem! Glad I seem to be helping!

What if I did it with variables and connected it with spawn proc?
I mean something like that
src.stunned = 0
spawn(duration) src.stunned = 1
src<<"you will get stunned in [duration]"

and what if I want to remove it (stun) before spawn procs? Can I do it or it's better way to do it?

For starters, yes, you can also set it up like that, so the Effect will take effect after a duration (and that would work for either method, using variables, or using a list)

However, I am not sure that there is a good method to "cancel" the stun before the duration runs out. You could set up a secondary system (with another variable) that can be used to check after spawn(duration) if the player can still be stunned, and then not do it if they can't be, but that's getting pretty messy, in my opinion.

But, even though doing this system with variables is possible, I don't think it is quite as flexible or elegant.

For one, you'll need to define and use a new variable for every possible status effect that you may plan to add (stunned, slowed, blinded, etc., etc.) This is generally a matter of personal preference, but I dislike having a ton of single-purpose, on/off, 1/0 toggled variables floating around my code like that.

However, if "stun" is going to be your only status effect, then it might make more sense to just use a variable.

Another thing to note, is that using an on (stunned=1) or off (stunned=0) switch on your status variables will only allow for those two states, with no good way to account for multiple stun instances from multiple sources (like your original example). If the first stun turns it on (stunned=1), the next stun will also turn it on (though it will already be on, so it technically won't do anything), then the duration of the first stun will turn it off, and the second (longer duration) stun will then be turned off/ignored.

If using variables, however, you could have them act as counters, to keep track of the total number of stuns the target has received:

src.stunned += 1 //or src.stunned++
spawn(duration) src.stunned -= 1 //or src.stunned--


This system will allow for the variable to keep track of the number of stuns that have been applied by increasing the value of the variable by 1 each time. Then it will count back down as each stun expires. Once it reaches back to 0, the player will no longer be stunned.

Ter13 wrote:

Actually, SSGX, your system is going to be a bit slower than the below:

> proc
> removeAll(var/list/l,var/ref)
> . = 1
> while(.)
> . = l.Remove(ref)
>

Remove won't return anything if it fails to remove something from the list, so we don't need to iterate through the list at all. We just need to check for the return value of remove.

That makes sense. But to me, this level of ultra-micro-management of efficiency is usually pointless, especially when it makes the code look that "abnormal" (to me, anyway; it just doesn't have the same level of readability)

If this particular function needed to run a million times, then making it as efficient as possible (by all means possible) makes sense. However, in this case, I wouldn't think people will be having status effects removed at hundreds of instances per tick or anything.

I'm fine with a proc that runs only every-so-often to be a little less than 100% efficient.
Normally, I'd agree with you, but in this case, since we're doing a write-once global proc, it makes sense to optimize it as much as possible.

If it were something we were going to be writing out explicitly hundreds of times... Well, I wouldn't be bothering.
In response to Ter13
Ter13 wrote:
Normally, I'd agree with you, but in this case, since we're doing a write-once global proc, it makes sense to optimize it as much as possible.

If it were something we were going to be writing out explicitly hundreds of times... Well, I wouldn't be bothering.

I suppose I can see that point. I'd just prefer to always have a decent balance between ultra-efficiency and having code that reads well (again, to me; I'm just not used to seeing/using super-optimized code, and in fact, I rarely ever use the . operator, period, so it looks so "foreign" to my eyes...lol), especially when the actual benefit is ultimately so small. Again, any procedure that's going to be called enough to tax the system should be optimized as highly as possible, but this one's not likely going to see that level of use. Why bother optimizing just for the sake of optimization?

But in regards to your second paragraph, I'd like to state that if you're explicitly writing the same procedure hundreds of times (or even dozens; hell, even more than half a dozen), then you've gone wrong somewhere... (of course, you know that; I'm just trying to be funny...lol)
In response to SuperSaiyanGokuX
SuperSaiyanGokuX wrote:
But in regards to your second paragraph, I'd like to state that if you're explicitly writing the same procedure hundreds of times (or even dozens; hell, even more than half a dozen), then you've gone wrong somewhere... (of course, you know that; I'm just trying to be funny...lol)

I only do that for trying to slim down code to be hyper-optimized that is going to be taxing to all get-out.

Sometimes, it's better to unroll a loop in order to reduce the number of instructions, or to remove the overhead (which is considerably more than I thought at first) of passing arguments for a function call, redefining variables, etc.

But you are right, for the most part, it's needless and gets in the way of readability. (Unless you know where to use it, and where to avoid it.) I also tend to leave commented out portions of code where I've unrolled huge portions of code, which show the unoptimized version for later reference.
Hello again, I have a lil bit newbie question, how can I get this work properly?
        checkMove(mob/Me)
if("stunned" || "feared" || "taunted" || "frozen" in Me.LISTstatuseffects) return 0
else return 1


For "stunned" everything is going well but for "feared" it doesn't. I suppose it should be written in a different way. Please correct me. Thanks!
I have problem with seeing the difference in || and &&. I mean I do understand the difference but like I now I should use one of them but dunno. One is disjunction and other one is conjuction and I understand it but can't remember those two symbols || &&.
It should look like this:

mob
check_move()
if(("stunned" in status_effects) || ("feared" in status_effects)) return 1
Page: 1 2