Re: DN's D&D4e Character Sheet Framework
Posted: Sun Mar 18, 2012 1:39 am
File is there for me.
Very strange. I just downloaded it without a problem. ???Angfiel wrote:I can't download the file because it says it doesn't excist.
That would be me and guilty as charged.(and our evil DM does knock me down frequently)
I've read a few posts in this thread regarding conditions and it seems that condition duration is kept to a minimum so as to keep control with the DM and not the framework itself.Also, how much work is it to implement automatic condition expiration?
As levels go up, conditions become very numerous, and it would be nice if, at the end of each turn, a dialogue would display the conditions set to expire at the end of that turn (with the option of extending each condition's duration - leaving control firmly in the hand of the user).
When I used to track initiative and conditions with pen and paper, instead of noting that at the beginning or end of someone's turn such a condition would expire, I would just note the end of the initiative count at the end of which it died (since you can't extend the duration of conditions by delaying, this works very well). So, if Larry had a condition that expired at the beginning of Sally's turn, and Sally had initiative 12, Next to Larry on the initiative sheet I just noted [condition](13) - to indicate that when init 12 rolled around, the condition was over. Could something like this be implemented?
Now I don't know how much of an authority he is on the subject so this may not actually be the case. I would agree with the sentiment that control should be with the DM. What interests me is a script that reminds you of when an effect expires so I'll be looking for that.1. No, the conditions are not cleared automatically, you have to do that manually. But it is possible to implement this behavior through a custom initiative script. However, the automation in this FW is intentionally kept low, because in the end it is the DM and not the FW that should run the game. For example it could be that some state would end, but you as the DM have some effect that makes it stay. In this case the script would not behave correctly.
That said, I have written a custom set of initiative macros for sorting initiative (with automatic tie breaking) and for advancing initiative. This script checks for all conditions and ongoing damage and tells you when people have to make a saving throw, when they take ongoing damage, and when effects end. If you want I could provide that script to you.
Yep, that's exactly what I was talking about. I hope he posts his script.MadJoker wrote:I have written a custom set of initiative macros for sorting initiative (with automatic tie breaking) and for advancing initiative. This script checks for all conditions and ongoing damage and tells you when people have to make a saving throw, when they take ongoing damage, and when effects end. If you want I could provide that script to you.
Hm, I have not checked this thread for far too long. Maybe the scripts can still be of help to someone.Ferghis wrote:Yep, that's exactly what I was talking about. I hope he posts his script.MadJoker wrote:I have written a custom set of initiative macros for sorting initiative (with automatic tie breaking) and for advancing initiative. This script checks for all conditions and ongoing damage and tells you when people have to make a saving throw, when they take ongoing damage, and when effects end. If you want I could provide that script to you.
Code: Select all
enableTurnTimer:0
CurrentTimer
printInitiative:0
Code: Select all
[IF(initiativeSize() == 0), CODE: {[H: removeAllFromInitiative()][H:abort(0)]};{};]
<!-- Check for states that end at the end of the encounter -->
[H: endEffect = 0]
[H: iniList = getInitiativeList()]
[H: tokenArr = json.get(iniList,"tokens")]
[foreach(token,tokenArr,""), CODE: {
[H: endEffects = checkInitiativeConditions(json.get(token,"tokenId"), 2)]
[H: saveEnd = json.get(endEffects,"saveEnd")]
[H: end = json.get(endEffects,"end")]
[H: endEffect = if(!json.isEmpty(saveEnd) || !json.isEmpty(end),1,endEffect)]
<span style="color: red">
[IF(!json.isEmpty(saveEnd)), CODE: {
<b>[r:getName(json.get(token,"tokenId"))]</b> must make a save against
};{}]
[foreach(condition,saveEnd," and "), CODE: {
[H: DNT_Condition = getProperty("DNA_Condition" + json.get(condition,"Slot"),json.get(token,"tokenId"))]
<b>[R: macroLink(json.get(DNT_Condition,"Name"), "Info@Lib:Sheet", "none", json.set("", "WindowType", "Condition", "Name", json.get(DNT_Condition,"Name"), "Duration", json.get(DNT_Condition, "Duration"), "Damage", json.get(DNT_Condition, "Damage"), "Penalty", json.get(DNT_Condition, "Penalty"), "Notes", json.get(DNT_Condition, "Notes")), json.get(token,"tokenId"))]</b>
}]
[IF(!json.isEmpty(saveEnd)), CODE: {
[R: if(json.length(saveEnd) > 1," conditions."," condition.")]
};{}]
[IF(!json.isEmpty(end)), CODE: {
<b>[r:getName(json.get(token,"tokenId")) + "'s"]</b>
};{}]
[foreach(condition,end," and "), CODE: {
[H: DNT_Condition = getProperty("DNA_Condition" + json.get(condition,"Slot"),json.get(token,"tokenId"))]
<b>[R: macroLink(json.get(DNT_Condition,"Name"), "Info@Lib:Sheet", "none", json.set("", "WindowType", "Condition", "Name", json.get(DNT_Condition,"Name"), "Duration", json.get(DNT_Condition, "Duration"), "Damage", json.get(DNT_Condition, "Damage"), "Penalty", json.get(DNT_Condition, "Penalty"), "Notes", json.get(DNT_Condition, "Notes")), json.get(token,"tokenId"))]</b>
}]
[IF(!json.isEmpty(end)), CODE: {
[R: if(json.length(end) > 1," conditions end."," condition ends.")]
};{}]
</span>
}]
[H: removeAllFromInitiative()]
[H:setLibProperty("CurrentTimer","","Lib:Information")]
[IF(!endEffect), CODE: {[H:abort(0)]};{}]
Code: Select all
[IF(initiativeSize() == 0), CODE: {[H:abort(0)]};{};]
[IF(getInitiativeRound() == -1), CODE: {[H:setInitiativeRound(-1)][H:setCurrentInitiative(-1)][H:setLibProperty("CurrentTimer",json.get(getInfo("server"),"timeInMs"),"Lib:Information")]};{};]
[H: enableTurnTimer = getLibProperty("enableTurnTimer","Lib:Information")]
[H: printInitiative = getLibProperty("printInitiative","Lib:Information")]
[H: enableTurnTimer = if(enableTurnTimer == "",0,enableTurnTimer)]
<!-- Check for states that end at the end of the turn -->
[H: endEffect = 0]
[H: tokenEffect = 0]
[H: prevTokenEffect = 0]
[H: iniList = getInitiativeList()]
[H: iniToken = getInitiativeToken()]
[H: tokenArr = if(getInitiativeRound() != -1 && iniToken != "" && iniToken != 0,json.get(iniList,"tokens"),"")]
[foreach(token,tokenArr,""), CODE: {
[H: tokenEffect = 0]
[H: endEffects = checkInitiativeConditions(json.get(token,"tokenId"), 0)]
[H: saveEnd = json.get(endEffects,"saveEnd")]
[H: end = json.get(endEffects,"end")]
[H: damage = json.get(endEffects,"damage")]
[H: tokenEffect = if(!json.isEmpty(saveEnd) || !json.isEmpty(end) || !json.isEmpty(damage),1,0)]
[H: endEffect = if(tokenEffect,1,endEffect)]
[R, If(tokenEffect && prevTokenEffect), CODE: {
[R: "<br>"]
};{}]
[H: prevTokenEffect = tokenEffect]
<span style="color: red">
[IF(!json.isEmpty(saveEnd)), CODE: {
<b>[r:getName(json.get(token,"tokenId"))]</b> must make a save against
};{}]
[foreach(condition,saveEnd," and "), CODE: {
[H: DNT_Condition = getProperty("DNA_Condition" + json.get(condition,"Slot"),json.get(token,"tokenId"))]
<b>[R: macroLink(json.get(DNT_Condition,"Name"), "Info@Lib:Sheet", "none", json.set("", "WindowType", "Condition", "Name", json.get(DNT_Condition,"Name"), "Duration", json.get(DNT_Condition, "Duration"), "Damage", json.get(DNT_Condition, "Damage"), "Penalty", json.get(DNT_Condition, "Penalty"), "Notes", json.get(DNT_Condition, "Notes")), json.get(token,"tokenId"))]</b>
}]
[IF(!json.isEmpty(saveEnd)), CODE: {
[R: if(json.length(saveEnd) > 1," conditions."," condition.")]
};{}]
[IF(!json.isEmpty(end)), CODE: {
<b>[r:getName(json.get(token,"tokenId")) + "'s"]</b>
};{}]
[foreach(condition,end," and "), CODE: {
[H: DNT_Condition = getProperty("DNA_Condition" + json.get(condition,"Slot"),json.get(token,"tokenId"))]
<b>[R: macroLink(json.get(DNT_Condition,"Name"), "Info@Lib:Sheet", "none", json.set("", "WindowType", "Condition", "Name", json.get(DNT_Condition,"Name"), "Duration", json.get(DNT_Condition, "Duration"), "Damage", json.get(DNT_Condition, "Damage"), "Penalty", json.get(DNT_Condition, "Penalty"), "Notes", json.get(DNT_Condition, "Notes")), json.get(token,"tokenId"))]</b>
}]
[IF(!json.isEmpty(end)), CODE: {
[R: if(json.length(end) > 1," conditions end."," condition ends.")]
};{}]
</span>
}]
[R, IF(iniToken != "" && iniToken != 0), CODE: {
[H: turnStartTime = getLibProperty("CurrentTimer","Lib:Information")]
[H: turnStartTime = if(turnStartTime != "",turnStartTime,json.get(getInfo("server"),"timeInMs"))]
[H: turnTime = json.get(getInfo("server"),"timeInMs") - turnStartTime]
[H, IF(isPC(iniToken)), CODE: {
[H: tSeconds = round(turnTime/1000)]
[H: tMinutes = floor(tSeconds/60)]
[H: tSeconds = tSeconds - floor( tSeconds / 60 ) * 60]
[H: broadcast("<span style='color: blue'><b>" + getName(iniToken) + "'s</b> turn took " + if(tMinutes > 0,tMinutes + " minute" + if(tMinutes > 1,"s","") + if(tSeconds > 0," and ",""),"") + if(tSeconds > 0 || tMinutes == 0, tSeconds + " second" + if(tSeconds > 1 || tSeconds == 0,"s",""),"") + ".</span>",if(enableTurnTimer,"all","gm"))]
};{}]
};{}]
<!-- Advance initiative until a token that is not dead is found -->
[H:nextInitiative()]
[H:isDead = getState("Dead",getInitiativeToken())]
[H:isDying = getState("Dying",getInitiativeToken())]
[H:isNPC = isNPC(getInitiativeToken())]
[WHILE(isDead || (isDying && isNPC),""), CODE: {
[H:nextInitiative()]
[H:isDead = getState("Dead",getInitiativeToken())]
[H:isDying = getState("Dying",getInitiativeToken())]
[H:isNPC = isNPC(getInitiativeToken())]
}]
[H: prevEndEffect = endEffect]
[H: received = 0]
[H: iniToken = getInitiativeToken()]
[R, IF(isPC(iniToken) && printInitiative), CODE:{
<span style="color:#3B9C9C">
[R: if(endEffect,"<br>","")]
<b>[r:getName(iniToken)]</b>
[R: " has received initiative."]
</span>
[H: received = 1]
};{}]
[H:setLibProperty("CurrentTimer",json.get(getInfo("server"),"timeInMs"),"Lib:Information")]
<!-- Check for states that end at the end of the turn -->
[H: endEffect = 0]
[H: tokenEffect = 0]
[H: prevTokenEffect = 0]
[H: iniList = getInitiativeList()]
[H: tokenArr = json.get(iniList,"tokens")]
[foreach(token,tokenArr,""), CODE: {
[H: tokenEffect = 0]
[H: endEffects = checkInitiativeConditions(json.get(token,"tokenId"), 1)]
[H: saveEnd = json.get(endEffects,"saveEnd")]
[H: end = json.get(endEffects,"end")]
[H: damage = json.get(endEffects,"damage")]
[H: tokenEffect = if(!json.isEmpty(saveEnd) || !json.isEmpty(end) || !json.isEmpty(damage),1,0)]
[H: endEffect = if(tokenEffect,1,endEffect)]
[R, If(tokenEffect && (received || prevEndEffect || prevTokenEffect)), CODE: {
[R: "<br>"]
};{}]
[H: prevTokenEffect = tokenEffect]
<span style="color: red">
[IF(!json.isEmpty(damage)), CODE: {
<b>[r:getName(json.get(token,"tokenId"))]</b> takes
};{}]
[foreach(condition,damage," and "), CODE: {
[r:condition]
}]
[IF(!json.isEmpty(damage)), CODE: {
.
};{}]
[IF(!json.isEmpty(end)), CODE: {
<b>[r:getName(json.get(token,"tokenId")) + "'s"]</b>
};{}]
[foreach(condition,end," and "), CODE: {
[H: DNT_Condition = getProperty("DNA_Condition" + json.get(condition,"Slot"),json.get(token,"tokenId"))]
<b>[R: macroLink(json.get(DNT_Condition,"Name"), "Info@Lib:Sheet", "none", json.set("", "WindowType", "Condition", "Name", json.get(DNT_Condition,"Name"), "Duration", json.get(DNT_Condition, "Duration"), "Damage", json.get(DNT_Condition, "Damage"), "Penalty", json.get(DNT_Condition, "Penalty"), "Notes", json.get(DNT_Condition, "Notes")), json.get(token,"tokenId"))]</b>
}]
[IF(!json.isEmpty(end)), CODE: {
[R: if(json.length(end) > 1," conditions end."," condition ends.")]
};{}]
</span>
}]
[IF(!endEffect && !prevEndEffect && !received), CODE: {[H:abort(0)]};{}]
Code: Select all
/self
[H: iniList = getInitiativeList()]
[H: tokenArr = json.get(iniList,"tokens")]
[H: tokenArr2 = tokenArr]
[H: ties = '{}']
[H, foreach(token1,tokenArr), CODE:{
[H: tid1 = json.get(token1,"tokenId")]
[H: ini1 = json.get(token1,"initiative")]
[H, foreach(token2,tokenArr2), CODE:{
[H: tid2 = json.get(token2,"tokenId")]
[H: ini2 = json.get(token2,"initiative")]
[H: ties = if(tid1 != tid2 && ini1 == ini2,json.set(ties,ini1,json.append(json.get(ties,ini1),tid1,tid2)),ties)]
}]
[H: tokenArr2 = json.remove(tokenArr2,0)]
}]
<span style="color: red">
[IF(json.length(ties) > 0), CODE: {
<b>Initiative ties</b> must be broken:<br>
};{}]
[foreach(tie,json.fields(ties),"<br>"), CODE:{
[H: tokensInTie = json.sort(json.unique(json.get(ties,tie)))]
[foreach(token,tokensInTie," - "), CODE:{
[R: getName(token)]
}]
[H: tokensInTie = json.shuffle(tokensInTie)]
[H: nrTokens = json.length(tokensInTie)]
[foreach(token,tokensInTie,""), CODE:{
[H, token(token): setInitiative(tie+(nrTokens/1000))]
[H: nrTokens = nrTokens - 1]
}]
}]
</span>
[H: sortInitiative()]
[H, if(json.length(ties) < 1), CODE: {[H:abort(0)]};{}]
[H:abort(0)]
Code: Select all
<!--
arg(0) - token that conditions should checked on
arg(1) - which conditions to check (0 - End of Turn, 1 - Start of Turn, 2 - End of Encounter)
-->
[H: assert(argCount() == 2,"<br>in macro <b>checkInitiativeConditions</b><br>Invalid number of arguments " + argCount() + ", expected 2")]
[H: type = arg(1)]
[H: iniList = getInitiativeList() ]
[H: tokenList = json.get(inilist,"tokens")]
[H: iniTokens = '[]']
[H, FOREACH(t,tokenlist), CODE:{
[H: iniTokens = json.append(iniTokens,json.get(t,"tokenId"))]
}]
[H: iniRound = getInitiativeRound()]
[H: tid = arg(0)]
[H, if(type != 2), CODE: {
[H: iid = getInitiativeToken()]
[H: iName = getName(iid)]
};{
[H: iid = ""]
[H: iName = ""]
}]
[H: tidx = json.indexOf(iniTokens,tid)]
[H: iidx = json.indexOf(iniTokens,iid)]
[H: saveEnd = '[]']
[H: end = '[]']
[H: damage = '[]']
[H: DNT_DurationList = getLibProperty("ConditionDurations", "Lib:Information")]
[H: DNA_ConditionIndex = getProperty("DNA_ConditionIndex",tid)]
[H, forEach(DNT_ConditionSlot, DNA_ConditionIndex, ""), CODE: {
[H: DNT_Condition = getProperty("DNA_Condition" + DNT_ConditionSlot,tid)]
[H: DNT_ConditionName = json.get(DNT_Condition, "Name")]
[H: DNT_ConditionDuration = json.get(DNT_Condition, "Duration")]
[H: DNT_ConditionDamage = json.get(DNT_Condition, "Damage")]
[H: DNT_ConditionPenalty = json.get(DNT_Condition, "Penalty")]
[H: DNT_ConditionRound = json.get(DNT_Condition, "Round")]
[H: DNT_ConditionSetBy = json.get(DNT_Condition, "SetBy")]
[H: DNT_ConditionNotes = json.get(DNT_Condition, "Notes")]
[H: DNT_ConditionFailedSaves = json.get(DNT_Condition, "FailedSaves") + 0]
[H: DNT_ConditionDurationTag = json.get(DNT_DurationList, DNT_ConditionDuration)]
<!-- "1":"Save Ends","2":"End of my Turn","3":"Start of my next Turn","4":"End of my next Turn" -->
<!-- "5":"Start of target's Turn","6":"End of target's Turn","7":"Encounter","8":"Other" -->
[H: endEffect = 0]
[IF(type == 0 && (DNT_ConditionDuration == "1" || DNT_ConditionName == "Dying") && tid == iid), CODE: {
[H: saveEnd = json.append(saveEnd,json.set("","Name",DNT_ConditionName,"Slot",DNT_ConditionSlot))]
};{}]
[IF(type == 1 && DNT_ConditionDuration == "4" && iName == DNT_ConditionSetBy), CODE: {
[H: DNT_Condition = json.set(DNT_Condition,"Duration","2"))]
[H: setProperty("DNA_Condition" + DNT_ConditionSlot,DNT_Condition,tid)]
};{}]
[SWITCH(DNT_ConditionDuration), CODE:
case "2": {
[h: endEffect = if(type == 0 && iName == DNT_ConditionSetBy,1,0)]
};
case "3": {
[h: endEffect = if(type == 1 && iName == DNT_ConditionSetBy,1,0)]
};
case "4": {
[h: endEffect = if(type == 0 && iName == DNT_ConditionSetBy && DNT_ConditionRound == iniRound + 1,1,0)]
};
case "5": {
[h: endEffect = if(type == 1 && tid == iid,1,0)]
};
case "6": {
[h: endEffect = if(type == 0 && tid == iid,1,0)]
};
case "7": {
[h: endEffect = if(type == 2,1,0)]
};
default: {
}]
[IF(endEffect), CODE: {
[H: end = json.append(end,json.set("","Name",DNT_ConditionName,"Slot",DNT_ConditionSlot))]
};{}]
[H, IF(type == 1 && DNT_ConditionDamage != "" && tid == iid), CODE: {
[H: dmgName = if(DNT_ConditionName == "Damage","","<b>" + lower(DNT_ConditionName) + "</b> ")]
[H: dmgName = dmgName + "<span style='color:black'>" + macroLink("damage", "Health@Lib:Execute", "none", json.set("", "Type", 0, "Amount", DNT_ConditionDamage), currentToken()) + "</span>"]
[H: damage = json.append(damage,"ongoing <b>" + DNT_ConditionDamage + "</b> " + dmgName)]
};{}]
}]
[H: dead = getState("Dead",tid)]
[H, IF(iniRound == -1 || dead), CODE:{
[H: macro.return = json.set("","saveEnd",'[]',"end",'[]',"damage",'[]')]
};{
[H: macro.return = json.set("","saveEnd",saveEnd,"end",end,"damage",damage)]
}]
Code: Select all
[h: defineFunction("checkInitiativeConditions", "checkInitiativeConditions@Lib:Execute")]