Author Topic: [WIP][Source Mod]Fair Psionics  (Read 8104 times)

Offline FudgeDragon

  • Captain
  • ***
  • Posts: 54
    • View Profile
[WIP][Source Mod]Fair Psionics
« on: May 10, 2014, 01:01:17 am »
Hey everyone. First potential mod out of five ideas I have written down on a piece of paper.
What this mod does: Forces aliens of follow the same Psionics rules as XCOM. Aliens can only target soldiers one of their units has already seen this turn.
What Inspired this mod: The new LOS option is nice, but really plays out as a heavy nerf to the aliens. It does somewhat limit XCOM Mind Control Cheese in the lategame, but makes it significantly easier to get there. When given the choice between mind controling a unit that it can see and just shooting it, the aliens seem to prefer to shoot. My mod seeks to even the playing field instead.

How it works(Warning:Gets Technical): When an alien decides to attack something (which it can do at any time the aliens know about any xcom units), if I can do Psi it tries that first. The AI then looks at each unit and picks the one it has the greatest chance go attack (it may at this stage just decide to shoot something if it figures its the better option). What my mod does is build a list of valid units first, from the units that each enemy unit has seen on its current turn. It then selects the best option from this new smaller list.

What this means in practice: Alien Psi plays out exactly like XCOM's. During the opening sweep of the map psionics will be largely unchanged, the only difference being that only units the aliens have seen in a particular turn are valid targets, so as you press the aliens back only your forward scout units can be paniced and controlled. Of course if the aliens get control of one of your scouts, it can then be used to find more targets. Once alien numbers begin to thin, the Psi troops lose their intel, until your troops burst through the last door for one final confrontation.

How can this be improved: The player typically uses his Psi troops as a reserve, waiting until he discovers enemy units to control. However if the Psi troop moves first and uses all its TUs because it has no valid targets it might miss out on an oppertunity later in the turn. This could be solved by making the Psi troop wait until after other units have moved, although that might introduce a problem with etherials constantly out waiting each other. Using rank as a rough guide in which order to move might solve the problem, although I have no idea how the aliens decide in which order to move. Considerning units can move multiple times a turn (with other units moving in between), it clearly isn't just iterating through an array.

Another possible improvement would be a better fix to the multiple psi attacks on a single unit problem. Currently if I am reading the code correctly, each Psi capable unit is restricted to a single Psi action each turn. While clearly a good fix for the aforementioned problem it somewhat limits their current potential. Possibly check current morale when deciding when to panic, or if a unit already is in FACTION_HOSTILE before attempting the mind control.

I'm interested in getting other peoples thoughts on the idea, and/or if you are a programmer, the implemetation.
if people want to try out my current code I've got a Github fork branch at github.com/fudgedragon/OpenXcom/tree/fairPsionics
or alternatively, I have a build attatched to this post. It only includes the changed exe (not to mention all the visual studio DLLs) and language files to keep the size down, so install over the current nightly.

EDIT: It occured to me after posting this last night that I didn't mention that it is a toggleable advanced option.
« Last Edit: May 10, 2014, 07:53:28 pm by FudgeDragon »

Offline Arpia

  • Colonel
  • ****
  • Posts: 116
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #1 on: May 10, 2014, 04:30:14 am »
Another thing I would like to see in the future is a more dynamic psi attack system.
Currently the two attacks have a hard coded success rate, I'd like that to be moddable. And the TU cost is the same regardless of your choice to panic or control a unit, I think it should be brought more in line with shooting, cost depending on the attack, just like the difference for snap/auto/aimed.
Perhaps even a small chance to miss, resulting in affecting the wrong target? (nearest unit to target)

I definitely think this is a better solution for alien units, though I think LoS rules should apply to xcom units.
anyway, just my thoughts.

Offline Warboy1982

  • Administrator
  • Commander
  • *****
  • Posts: 2333
  • Developer
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #2 on: May 10, 2014, 08:09:33 am »
there's an easier way than iterating unitsSpottedThisTurn, check if the target's turnsSinceSpotted is 0. that amounts to the same thing, but accounts for aliens that spotted a unit and then died from reaction fire.

and no, it's not restricted to one psi attack per turn, it's restricted to one psi attack in a row. it must make a regular move or attack before it is allowed to psi attack again this turn, which counts as a "free" action, meaning the alien can make a psi attack and move up to 6 times per turn.

it IS iterating through an array, ordered by deployment data (terror units move first in alien bases, for example). it performs 3 actions per unit, and when it reaches the end of the array, it starts again from the beginning for the second phase. (hence 6 actions per turn)

it DOES check current faction before making a mind control attempt, that's what validTarget() is for, and it does factor morale in to the decision to make a panic or mind control attempt, the lower the morale, the greater the odds of mind control instead of panic
« Last Edit: May 10, 2014, 08:16:09 am by Warboy1982 »

Offline FudgeDragon

  • Captain
  • ***
  • Posts: 54
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #3 on: May 10, 2014, 10:01:21 am »
Another thing I would like to see in the future is a more dynamic psi attack system.
Currently the two attacks have a hard coded success rate, I'd like that to be moddable. And the TU cost is the same regardless of your choice to panic or control a unit, I think it should be brought more in line with shooting, cost depending on the attack, just like the difference for snap/auto/aimed.
Perhaps even a small chance to miss, resulting in affecting the wrong target? (nearest unit to target)

I definitely think this is a better solution for alien units, though I think LoS rules should apply to xcom units.
anyway, just my thoughts.

Hmm, A little outside of what I'm trying to do here, but maybe I can look into it later.
We'd have to introduce some new ruleset values for psi weapons, splitting tu use into panic and mind control, and adding some sort of multiplier for cost?

On a sidenote making only XCOM use LOS can be done with rulesets. I think there might be one floating about the forums somewhere (or did I see it in suggestions on github, I dont recall.)

there's an easier way than iterating unitsSpottedThisTurn, check if the target's turnsSinceSpotted is 0. that amounts to the same thing, but accounts for aliens that spotted a unit and then died from reaction fire.

and no, it's not restricted to one psi attack per turn, it's restricted to one psi attack in a row. it must make a regular move or attack before it is allowed to psi attack again this turn, which counts as a "free" action, meaning the alien can make a psi attack and move up to 6 times per turn.

it IS iterating through an array, ordered by deployment data (terror units move first in alien bases, for example). it performs 3 actions per unit, and when it reaches the end of the array, it starts again from the beginning for the second phase. (hence 6 actions per turn)

it DOES check current faction before making a mind control attempt, that's what validTarget() is for, and it does factor morale in to the decision to make a panic or mind control attempt, the lower the morale, the greater the odds of mind control instead of panic

OOOH, yes turnsSinceSpotted() would do the job. I take it dead units can't be found in the GetUnits() vector, which makes sense, otherwise we'd have to keep checking if the target was alive. Thanks for the information Warboy!

Also thanks for clarifying the movement order stuff, So if he doesn't have any psi targets in the first part of his turn, he gets to go up to three more times after everyone else has moved.

My confusion on the _didPsi varable was I dimly remembered commit 60f570c - fix multiple MC scenario (from 6 months ago), I assumed when I saw it that this was the fix. I see now that it was unrelated.

In fact this addresses all my concerns for improvement.

I'll update the first post (and the git) when I've changed over to turnsSinceSpotted() (and confirmed it still works nicely)

EDIT: Just invalidated all my testing. Forgot to turn my own advanced option on. Etherials were working as Gollop intended.
« Last Edit: May 10, 2014, 10:26:35 am by FudgeDragon »

Offline Sectoid_Soldier

  • Sergeant
  • **
  • Posts: 43
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #4 on: May 10, 2014, 10:17:26 am »
This sounds really good. I'll be trying this mod ASAP.

Offline FudgeDragon

  • Captain
  • ***
  • Posts: 54
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #5 on: May 10, 2014, 10:47:50 am »
Hmm, looks like getTurnsSinceSpotted doesn't work quite the same way. getTurnsSinceSpotted is only iterated at the start of each player turn. So If I let them see a unit during my movement, they can take control of it during theirs without scouting for it. Not quite what I was going for, but It works soo much better than iterating. Decisions Decisions.

Offline Warboy1982

  • Administrator
  • Commander
  • *****
  • Posts: 2333
  • Developer
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #6 on: May 10, 2014, 11:14:17 am »
dead units ARE in the getUnits() vector, but validTarget() excludes them by checking isOut()

you could actually force this behaviour in the regular build by setting the alien's intelligence to 0 with a ruleset, btw.

Offline FudgeDragon

  • Captain
  • ***
  • Posts: 54
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #7 on: May 10, 2014, 11:51:02 am »
Ah OK, in that case wouldn't my iterator already be checking units killed by reaction fire. My understanding reading the code, is until the next turn, dead units should still be carrying their unitsSpottedThisTurn information.

I also thought taht setting alien's intelligence to 0 would affect them adversely in other ways as well. Is this not the case?

Offline Warboy1982

  • Administrator
  • Commander
  • *****
  • Posts: 2333
  • Developer
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #8 on: May 10, 2014, 01:25:19 pm »
i guess that's true, although it still seems horribly inefficient, maybe  iterating all friendly units' spotted units and inserting them into a seperate vector for choosing a target, rather than iterating friendly units' spotted units while iterating all enemy units? if that makes sense?
Code: [Select]
std::map<int, BattleUnit*> PsiTargets;
for (std::vector<BattleUnit*>::const_iterator *i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
{
       if ((*i)->getFaction() != FACTION_HOSTILE) continue;
       for (std::vector<BattleUnit*>const_iterator j = (*i)->getUnitsSpottedThisTurn()->begin(); j != (*i)->getUnitsSpottedThisTurn()->end(); ++j)
       {
              PsiTargets[(*i)->getId()] = *i;
       }
}

for (std::map<int, BattleUnit*>::const_iterator i = PsiTargets.begin(); i != PsiTargets.end(); ++i)
{
... etc

as for adverse effects from setting intelligence to 0... no, the only function this stat serves is how long the alien knows where your guy is after visual contact is lost.

Offline Sectoid_Soldier

  • Sergeant
  • **
  • Posts: 43
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #9 on: May 10, 2014, 01:36:04 pm »
i guess that's true, although it still seems horribly inefficient, maybe  iterating all friendly units' spotted units and inserting them into a seperate vector for choosing a target, rather than iterating friendly units' spotted units while iterating all enemy units? if that makes sense?
Code: [Select]
std::map<int, BattleUnit*> PsiTargets;
for (std::vector<BattleUnit*>::const_iterator *i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
{
       if ((*i)->getFaction() != FACTION_HOSTILE) continue;
       for (std::vector<BattleUnit*>const_iterator j = (*i)->getUnitsSpottedThisTurn()->begin(); j != (*i)->getUnitsSpottedThisTurn()->end(); ++j)
       {
              PsiTargets[(*i)->getId()] = *i;
       }
}

for (std::map<int, BattleUnit*>::const_iterator i = PsiTargets.begin(); i != PsiTargets.end(); ++i)
{
... etc

as for adverse effects from setting intelligence to 0... no, the only function this stat serves is how long the alien knows where your guy is after visual contact is lost.

There would be adverse affects from setting all intel's to 0, methinks. The alien in question then may attack, hide, or what else, I suppose. Would the other aliens 'know' where XCOM soldiers are also?

Offline Warboy1982

  • Administrator
  • Commander
  • *****
  • Posts: 2333
  • Developer
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #10 on: May 10, 2014, 02:17:11 pm »
yes, but only until the end of the turn.

Offline FudgeDragon

  • Captain
  • ***
  • Posts: 54
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #11 on: May 10, 2014, 05:11:46 pm »
Well. This is certainly making me reconsider what I knew about psionics. It always used to seem like the aliens just mind controlled whoever they wanted. But ValidTarget should prevent that (prior to turn 20). Must be a consequence of the lack of doors on the skyranger and avenger. Still I would think adjusting intel down would make targeting for blasterbombs and ambushes harder for the aliens.

Your suggested solution seems good, although I'm having trouble leaving it as optional. I can build the PsiTargets map using code similar to what you suggested (and I gather we are using map instead of vector so that we can specify the ID of the inserted item, to avoid inserting more than once), and I'm even thinking it might be worth doing the same for VisibleUnits for the LOSRequired section as I would imagine std::find is iterating through each item as well.

The problem I am having is using the new map when Option::fairPsionics is true but using the original getUnits vector when false. Short of rebuilding the original vector as a map (which is going to involve iterating through it again) I'm not sure of the best way to do it.

Offline Warboy1982

  • Administrator
  • Commander
  • *****
  • Posts: 2333
  • Developer
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #12 on: May 10, 2014, 05:47:55 pm »
yeah, i suggested a map simply to avoid adding the same entry to the array twice, you could do similarly with a vector by using std::find() first i guess, but i was going for the more optimal route, as i didn't realize you were going for an option.

Offline FudgeDragon

  • Captain
  • ***
  • Posts: 54
    • View Profile
Re: [WIP][Source Mod]Fair Psionics
« Reply #13 on: May 10, 2014, 06:18:06 pm »
With using an option I ended up with this
Code: [Select]
https://Create a list of valid targets for psionics
std::map<int, BattleUnit*> PsiTargets;
https://Check if line of sight required first, if true the other two become irrelevent, as only units visible to this one matter, and we have a list for that.
if (LOSRequired)
{
for (std::vector<BattleUnit*>::const_iterator i = _unit->getVisibleUnits()->begin(); i != _unit->getVisibleUnits()->end(); ++i)
{
https://If we check these conditions now we potentialy reduce the number of things we iterate later
if (validTarget(*i, true, false) && (*i)->getOriginalFaction() == FACTION_PLAYER && (*i)->getArmor()->getSize() == 1)
{
PsiTargets[(*i)->getId()] = *i;
}
}
}
https://Otherwise start iterating over the entire units collection.
else
{
for (std::vector<BattleUnit*>::const_iterator i = _save->getUnits()->begin(); i != _save->getUnits()->end(); ++i)
{
https://If fair psionics is switched on
if (Options::fairPsionics)
{
https://we care about FACTION_HOSTILE units only
if ((*i)->getFaction() == FACTION_HOSTILE)
{
https://iterate through each units list of units spotted during its turn
for (std::vector<BattleUnit*>::const_iterator j = (*i)->getUnitsSpottedThisTurn().begin(); j != (*i)->getUnitsSpottedThisTurn().end(); ++j)
{
https://checking these now so we dont have to later
if (validTarget(*j, true, false) && (*j)->getOriginalFaction() == FACTION_PLAYER && (*j)->getArmor()->getSize() == 1)
{
PsiTargets[(*j)->getId()] = *j;
}
}
}
}
else
{
https://Otherwise just check our standard conditions
if (validTarget(*i, true, false) && (*i)->getOriginalFaction() == FACTION_PLAYER && (*i)->getArmor()->getSize() == 1)
{
PsiTargets[(*i)->getId()] = *i;
}
}
}
}
https://Then we iterate through everything we selected in the last stage and evalutate psi odds
for (std::map<int, BattleUnit*>::const_iterator i = PsiTargets.begin(); i != PsiTargets.end(); ++i)
{
int chanceToAttackMe = psiAttackStrength
+ (((*i).second->getStats()->psiSkill > 0) ? (*i).second->getStats()->psiSkill * -0.4 : 0)
- _save->getTileEngine()->distance((*i).second->getPosition(), _unit->getPosition())
- ((*i).second->getStats()->psiStrength)
+ RNG::generate(55, 105);

if (chanceToAttackMe > chanceToAttack)
{
chanceToAttack = chanceToAttackMe;
_aggroTarget = (*i).second;
}
}
Which compared to existing code feels like a horrible monstrocity, but I've minimised reuse of iterators where I can.  I tried compacting the Ifs a bit but doing so changed the meanings and produced more futher down. Thoughts?

EDIT: Added some comments to explain what the hell I was thinking.
« Last Edit: May 10, 2014, 07:22:34 pm by FudgeDragon »