Doviello+

That or have it apply a Blocker EffectPromotion to all units in the stack, and have Accept Challenge clear that promotion from all units in the stack. Not sure which would be faster/easier. But to apply a blocking promotion to the entire stack and an accepting promotion to only yourself would require python, so either way you'll need to use some python, or just make the player keep from having more than 1 challenger in a stack himself, or suffer one being used at random from those who volunteered.
 
Sorry I know I am simply Falling Further off the thread...

How about making Forts/Castles/Citadels/Villages/Towns under Bannor control spawn defenders when enemies approach (at least under their civic)? allowing the Bannor to cast Recruit in Forts/Castles/Citadels/Villages/Towns instead of just cities? (Also, making these and normal Crusade spawns--assuming we don't get rid of those in favor of a system that give you more control or make them only spawn where needed--get weapons promotions without having to go to a city first.)

I think something like this would be very interesting for the Bannor. What would you think of coding it similar to the Trents/Ancient forest. % chance to spawn when an enemy steps into a square with Forts/Castles/Citadels/Villages/Towns. Each could have their own % chance to occur
 
Alright, looks like I have the Duel system working. Still have to block other units from issueing a challenge after one already has, but now it's actually working that should be simple.

Basically, you must have at least two units in a stack to cast Issue Challenge, which does nothing but add an effect promotion to the unit, and allow other units in the stack to cast Accept Challenge. This spell searches the stack for the unit with the effect promotion from Issue Challenge, and then, based on a random number, will:
  1. Kill the Defender, and grant the Challenger 1/4th of his experience, +2 in case he doesn't have any.
  2. Same, for the Defender rather than Challenger.
  3. 10% chance for both to survive, taking massive damage and granting 1/10th of the other units xp to each unit. This time, there is no xp boost in case the other unit has none.
  4. 4% chance for both units to die.
 
1/4 of the experience seems a bit small.

Why did you choose that? why not 1/2, or all. All would probably be too much, but I think a quarter is too little.

Also, is the use of a random number necessary? Why not base it on a rough approximation of the unit's strength, based on it's combat strength and level.

Something like strength = combatstrength * (unitlevel *0.5)

entirely Random numbers just seems so arbitrary.
 
For now I just want it working, so I put in the first values that popped into my head. Obviously, they'll change as I balance them.

The combat value is something I'd like to do, I'm just not sure how to go about it yet. :lol:

It would be easy to make the stronger unit win, but to tie it to actual combat odds is much more difficult.
 
Might actually have the combat calculation working, or at least, a weighted random number system.

I've got:
iRnd = CyGame().getSorenRandNum(100, "Doviello Duel")
iChallenger = pUnit.baseCombatStr() * (pUnit.getlevel() * 0.5)
iDefender = caster.baseCombatStr() * (caster.getlevel() * 0.5)
iCombat = iChallenger - iDefender
iWeightedRnd = iRnd - iCombat

If the Challenger is stronger, the Rnd will be lower. If he's weaker, the Rnd will be higher. Because of that, a rnd of 48-52 results in death for both, and a range of 10 around that will be a draw. Below, challenger wins. Above, defender wins. Just gotta test it now.
 
It would be easy to make the stronger unit win, but to tie it to actual combat odds is much more difficult.

I gather that much. I wasn't suggesting to tie it to actual combat odds. Merely to create a rough approximation of them. Ie, not taking into account what the promotions a unit has actually are, just what level it is, and it's base strength.

And actually, come to think of it, perhaps a 0.5 weighting on the level would be too much. That's essentialyl a 50% bonus in the calculation per level.

How about 0.2 instead, but +1 in addition. So something like...

iRnd = CyGame().getSorenRandNum(100, "Doviello Duel")
iChallenger = pUnit.baseCombatStr() * ((pUnit.getlevel() * 0.2)+1)
iDefender = caster.baseCombatStr() * ((caster.getlevel() * 0.2)+1)
iCombat = iChallenger - iDefender
iWeightedRnd = iRnd - iCombat

This would be roughly equivilant to the unit picking Combat with each level.
 
I'm not all that sure. Kael never really answered my questions about it.

Considering that isWorldWonderClass(iBuildingClass) is just isWorldWonderClass(iBuildingClass) and not something.isWorldWonderClass(iBuildingClass) and that it is defined in the same file, I guess you can just say getCombatOdds(pChallenger, pDefender)


I think most functions that we pass a unit take CyUnit instead of CvUnit, but I'm not sure if it makes a difference.

I didn't get an error when I had iOdds = getCombatOdds(caster, pUnit) in the part of the code that decides what unit the challenger should fight (it was suppose to fight the melee unit it had the lowest change of beating; I hadn't gotten around to basing the actual odds of survival or the xp gain on this function), but I don't think the code as a whole worked. However, that might have been because I used if caster != pUnit: instead of if caster.getID != pUnit.getID:





Looking in the SDK, I see the int it returns is in the range of 0 to 1000. I think it is 10 x the odds that would be displayed in the mouseover, so that it can show a value of 99.9 without needing to be a floating point variable and so take up more space



I wonder if there is another function that would tell you how much xp the unit would have gotten from defeating the other in normal combat.
 
So, iChallengerOdds = getCombatOdds(pUnit, caster) does indeed work. That gives the odds for the Challenger attacking the Defender... In other words, the unit who cast the first spell vs the unit who cast the other. I have another variable for the reverse... Comparing the two determines which block to send it to, where I get a rand. If the rand is HIGHER than the combat odds, the unit the odds applied to dies. If LOWER, the unit wins. There is a separate 10% chance for both to survive, and a 2% chance for both to die. Those odds likely need to be balanced.

Here's an example:

You have two units. The first, or the Challenger, casts Issue Challenge. The second, or the Defender, casts Accept Challenge in response. Your Challenger has a higher strength, so the function moves to the Challenger section. Two new rands are called. The first determines whether there is a tie or both units die. If neither are true, the Challenger's combat odds are compared against the second rand. If the rand is higher than his odds, he dies. So if he has a 90% chance to win, there is only a 10% chance for death.

In case both units have the exact same odds, the second rand is simply used by itself... >50, challenger dies. <50, defender dies.

I might introduce a third rand before the combat odds are compared the first time... so iChallengerRnd = (iRnd + (iChallengerOdds * 2)) / 3 rather than just iChallengerOdds = getCombatOdds(pUnit, caster).
 
Here's the code, for those more knowledgeable than I am to pick apart:

Spoiler :
Code:
def spellDovielloDuel(caster):
	pPlot = caster.plot()
	iChallengerProm = gc.getInfoTypeForString('PROMOTION_CHALLENGER')
	for i in range(pPlot.getNumUnits()):
		pUnit = pPlot.getUnit(i)
		iRnd = CyGame().getSorenRandNum(100, "Doviello Duel")
		if pUnit.isHasPromotion(iChallengerProm) == True:	
			iChallengerOdds = getCombatOdds(pUnit, caster)
			iChallengerRnd = (iRnd + (iChallengerOdds * 2)) / 3
			iDefenderOdds = getCombatOdds(caster, pUnit)
			iDefenderRnd = (iRnd + (iDefenderOdds * 2)) / 3
			if iDefenderOdds > iChallengerOdds:
				iDefenseRnd = CyGame().getSorenRandNum(100, "Doviello Duel Defender")
				iDefenseTieRnd = CyGame().getSorenRandNum(100, "Doviello Duel Defender Tie")
				if iDefenseTieRnd < 10:
					caster.changeExperience(pUnit.getExperienceTimes100() / 10, -1, False, False, False)
					caster.setDamage(75, caster.getOwner())
					pUnit.changeExperience(caster.getExperienceTimes100() / 10, -1, False, False, False)
					pUnit.setDamage(75, pUnit.getOwner())
					pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CHALLENGER'), False)
					CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_DOVIELLO_DUEL_DRAW", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(8),caster.getX(),caster.getY(),True,True)
				elif (iDefenseTieRnd <= 12 and iDefenseTieRnd >= 10):
					CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_ARENA_DEATH", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(7),caster.getX(),caster.getY(),True,True)
					caster.kill(True, PlayerTypes.NO_PLAYER)
					pUnit.kill(True, PlayerTypes.NO_PLAYER)
				elif iDefenseTieRnd > 12:
					if iDefenseRnd > iDefenderOdds:
						pUnit.changeExperience((caster.getExperienceTimes100() / 4) + 2, -1, False, False, False)
						pUnit.setDamage(25, pUnit.getOwner())
						pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CHALLENGER'), False)
						CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_DOVIELLO_DUEL_LOSS", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(8),caster.getX(),caster.getY(),True,True)
						caster.kill(True, PlayerTypes.NO_PLAYER)
					if iDefenseRnd < iDefenderOdds:
						caster.changeExperience((pUnit.getExperienceTimes100() / 4) + 2, -1, False, False, False)
						caster.setDamage(25, caster.getOwner())
						CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_DOVIELLO_DUEL_WIN", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(8),caster.getX(),caster.getY(),True,True)
						pUnit.kill(True, PlayerTypes.NO_PLAYER)
			if iChallengerOdds > iDefenderOdds:
				iChallengeRnd = CyGame().getSorenRandNum(100, "Doviello Duel Challenger")
				iChallengerTieRnd = CyGame().getSorenRandNum(100, "Doviello Duel Challenger Tie")
				if iChallengerTieRnd < 10:
					caster.changeExperience(pUnit.getExperienceTimes100() / 10, -1, False, False, False)
					caster.setDamage(75, caster.getOwner())
					pUnit.changeExperience(caster.getExperienceTimes100() / 10, -1, False, False, False)
					pUnit.setDamage(75, pUnit.getOwner())
					pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CHALLENGER'), False)
					CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_DOVIELLO_DUEL_DRAW", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(8),caster.getX(),caster.getY(),True,True)
				elif (iChallengerTieRnd <= 12 and iChallengerTieRnd >= 10):
					CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_ARENA_DEATH", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(7),caster.getX(),caster.getY(),True,True)
					caster.kill(True, PlayerTypes.NO_PLAYER)
					pUnit.kill(True, PlayerTypes.NO_PLAYER)
				elif iChallengerTieRnd > 12:
					if iChallengeRnd < iChallengerOdds:
						pUnit.changeExperience((caster.getExperienceTimes100() / 4) + 2, -1, False, False, False)
						pUnit.setDamage(25, pUnit.getOwner())
						pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CHALLENGER'), False)
						CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_DOVIELLO_DUEL_LOSS", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(8),caster.getX(),caster.getY(),True,True)
						caster.kill(True, PlayerTypes.NO_PLAYER)
					if iChallengeRnd > iChallengerOdds:
						caster.changeExperience((pUnit.getExperienceTimes100() / 4) + 2, -1, False, False, False)
						caster.setDamage(25, caster.getOwner())
						CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_DOVIELLO_DUEL_WIN", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(8),caster.getX(),caster.getY(),True,True)
						pUnit.kill(True, PlayerTypes.NO_PLAYER)
			if iChallengerOdds == iDefenderOdds:
				iEqualRnd = CyGame().getSorenRandNum(100, "Doviello Duel Equals")
				iEqualTieRnd = CyGame().getSorenRandNum(100, "Doviello Duel Equals Tie")
				if iEqualTieRnd < 10:
					caster.changeExperience(pUnit.getExperienceTimes100() / 10, -1, False, False, False)
					caster.setDamage(75, caster.getOwner())
					pUnit.changeExperience(caster.getExperienceTimes100() / 10, -1, False, False, False)
					pUnit.setDamage(75, pUnit.getOwner())
					pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CHALLENGER'), False)
					CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_DOVIELLO_DUEL_DRAW", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(8),caster.getX(),caster.getY(),True,True)
				elif (iEqualTieRnd <= 12 and iEqualTieRnd >= 10):
					CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_ARENA_DEATH", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(7),caster.getX(),caster.getY(),True,True)
					caster.kill(True, PlayerTypes.NO_PLAYER)
					pUnit.kill(True, PlayerTypes.NO_PLAYER)
				elif iEqualTieRnd > 12:
					if iEqualRnd < 50:
						pUnit.changeExperience((caster.getExperienceTimes100() / 4) + 2, -1, False, False, False)
						pUnit.setDamage(25, pUnit.getOwner())
						pUnit.setHasPromotion(gc.getInfoTypeForString('PROMOTION_CHALLENGER'), False)
						CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_DOVIELLO_DUEL_LOSS", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(8),caster.getX(),caster.getY(),True,True)
						caster.kill(True, PlayerTypes.NO_PLAYER)
					if iEqualRnd >= 50:
						caster.changeExperience((pUnit.getExperienceTimes100() / 4) + 2, -1, False, False, False)
						caster.setDamage(25, caster.getOwner())
						CyInterface().addMessage(caster.getOwner(),True,25,CyTranslator().getText("TXT_KEY_MESSAGE_DOVIELLO_DUEL_WIN", ()),'',1,'Art/Interface/Buttons/Buildings/Arena.dds',ColorTypes(8),caster.getX(),caster.getY(),True,True)
						pUnit.kill(True, PlayerTypes.NO_PLAYER)
 
Just a little guess here. Do you have a for loop at the start?

It looks like, if you get multiple people to issue challenges, the one who accepts would have to fight them all in rapid succession. Is this correct?

Also, it seems that you get the combat odds twice near the start, one for challenger andone for accepter. Why not just get the odds once, and then invert them for the second one? seems like something that could be optimised.

I'm not being very helpful, just trying to see if I can understand python. My knowledge of it is almost non existent.
 
Honestly, mine isn't much better. :lol:

There is in fact a loop, but it checks for an effect promotion gained by issueing a challenge. As long as that promotion exists in the stack, no other unit can issue a challenge. Therefore, unless you bring a challenger from another stack, there should only ever be one unit for you to fight. ;) Without that check, yes, it would fight everyone.

I got the combat odds twice because the odds for a warrior to attack a champion are very different for a champion to attack a warrior. Since I've never used the call before, and as far as I can tell no other function does either, I'd rather just use what I KNOW works, than try a more elegant solution. :lol:

While I'm talking about it, I'm thinking of putting a check in for a worker/slave, and if it wins, promote it to a warrior.
 
I got the combat odds twice because the odds for a warrior to attack a champion are very different for a champion to attack a warrior. Since I've never used the call before, and as far as I can tell no other function does either, I'd rather just use what I KNOW works, than try a more elegant solution. :lol:

I said invert the odds.

Like, if a champion has 90% combat odds, is it not logical that the warrior would have 10% ?

In this case, since both units are standing in the same tile, the circumstances for either way around should be the same.


Although......
I wonder if it's taking city defence into account. Perhaps standing in a fortified city would give you a huge bonus on the defence. That could cause some issues.
 
I said invert the odds.

Like, if a champion has 90% combat odds, is it not logical that the warrior would have 10% ?

In this case, since both units are standing in the same tile, the circumstances for either way around should be the same.


Although......
I wonder if it's taking city defence into account. Perhaps standing in a fortified city would give you a huge bonus on the defence. That could cause some issues.

I didn't do it that way because units can have different Attack/Defense stats. Take a berserker... Attacking it you'll have higher odds than you would defending.
 
I believe the combat odds do take city, terrain, hill, and feature defense into account, so the odds would be different based on who attacks whom. Oh, and in FfH there is also a difference between attack and defense strength.



Bringing in a challenger from another stack seems like something that could happen fairly often. It might be a good idea to put a break at the end of the code to end the for loop and make the caster only accept one challenge.
 
Back
Top Bottom