RPG Battle Engine - Alpha 7.5, downloads in first post

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Message
Author
Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#151 Post by Jake »

CaseyLoufek wrote:Same effect. Radius has a default value so that wasn't the problem.

My GetOccupants code was wrong but it doesn't even reach that line.
Gah! Stupid error on my part. Change this:

Code: Select all

        def GetPositionsInRadius(self, start, radius=1, callback=None):
            # For this, we're actually better off using the PathBattlefield implementation
            # than the parent GridBattlefield implementation.
            
            PathBattlefield.GetPositionsInRadius(self, start, radius=radius, callback=callback)
to this:

Code: Select all

        def GetPositionsInRadius(self, start, radius=1, callback=None):
            # For this, we're actually better off using the PathBattlefield implementation
            # than the parent GridBattlefield implementation.
            
            return PathBattlefield.GetPositionsInRadius(self, start, radius=radius, callback=callback)
Spot the difference!



(On the plus side, since I forgot you were using hexes, I found and fixed a bug in the square-grid version of GetPositionsInRadius while I was looking for this. ;-)
Server error: user 'Jake' not found

CaseyLoufek
Regular
Posts: 142
Joined: Sat May 28, 2011 1:15 am
Projects: Bliss Stage, Orbital Knights
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#152 Post by CaseyLoufek »

Wow I totally missed that too, even when I went in and tried to check the PathBattlefield.GetPositionsInRadius() for bugs. Thank you.

CaseyLoufek
Regular
Posts: 142
Joined: Sat May 28, 2011 1:15 am
Projects: Bliss Stage, Orbital Knights
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#153 Post by CaseyLoufek »

It works but unlike my previous method seems to be pulling Scenary which I hadn't yet designed to interact with my combat rules.

At least I think that's what's happening.

Now I'm getting Float * NoneType errors for this:

Code: Select all

combatDamage = (self.AttackDamage *damageRate)-((self.AttackDamage *damageRate * combatResist) / 100)-combatArmor
But the function itself is this:

Code: Select all

def ActionHitTarget(self, fighter, t, distance=1, critChance=10, missChance=0, damageRate=1.0):
and I set combatResist to 0 as a failsafe at the begining. None of those values can BE a NoneType.

At little code-algebra shows self.AttackDamage HAS to the problem

Code: Select all

combatDamage = (self.AttackDamage *Safe )-((self.AttackDamage *Safe * Safe ) / 100)-combatArmor
But attack damage CAN'T be the problem, it's on the abilty itself and didn't change.

So there's something really weird about your system or mine that isn't immediately obvious and wasn't an issue until this particular interaction occured. I never got anything remotely like this with my old method of getting targets in range. I also do not get these errors if I take the Scenary off the map or when an attack is totally out of the way of Scenary.

The full major code changes for my AttackSkill subclass:

Code: Select all

            def PerformAction(self, fighter, target):
                #check all enemy fighters for an in-range PDS attack
                for pdsFighter in fighter._battle.Fighters:
                    if pdsFighter.Faction != fighter.Faction and (pdsFighter.Active == True):           
                        for pdsSkill in pdsFighter._skillHandlers:
                            if hasattr(pdsFighter._skillHandlers[pdsSkill], 'PDS'):
                                if pdsFighter._skillHandlers[pdsSkill].Used == False:
                                    distance = fighter._battle.GetRange(fighter.Position, pdsFighter.Position) - 1
                                    if pdsFighter._skillHandlers[pdsSkill].Range > distance:
                                        pdsFighter._skillHandlers[pdsSkill].ActionHitTarget(pdsFighter, fighter, distance, 0, 10, 1.0)
                                        pdsFighter._skillHandlers[pdsSkill].Used = True
                t = target[0]
                distance = fighter._battle.GetRange(t.Position, fighter.Position) - 1
                if hasattr(self, 'Ammo'):
                    self.Ammo -= 1
                self.ActionHitTarget(fighter, t, distance, 5, 5, 1.0)
                #spray fire AoE check
                if hasattr(self, 'Spray'):
                    for spraySpace in fighter._battle.Battlefield.GetPositionsInRadius(t.Position, radius=1):
                        for sprayFighter in fighter._battle.Battlefield.GetOccupants(spraySpace):
                            distance = fighter._battle.GetRange(t.Position, sprayFighter.Position)
                            if (distance < 1.25) and (sprayFighter != t) and (sprayFighter != fighter) and (sprayFighter.Active == True):
                                distance = fighter._battle.GetRange(sprayFighter.Position, fighter.Position) - 1
                                self.ActionHitTarget(fighter, sprayFighter, distance, 0, 10, 0.5)
                #destroy missile, replace with remove fighter later
                if hasattr(self, 'Selfdestruct'):
                    fighter.Die()
                #We set the skill as used now and resume the turn
                self.Used = True
                
            def ActionHitTarget(self, fighter, t, distance=1, critChance=10, missChance=0, damageRate=1.0):
                missChance += (distance * 2)
                damageFalloff = distance
                combatStat = fighter.GetStat(self.AttackStat)
                combatResist = 0
                if hasattr(self, 'DamageType'):
                    combatArmor =  t.GetStat(self.DamageType+"Armor")
                    combatResist =  t.GetStat(self.DamageType+"Resist")
                if hasattr(self, 'DamageType2'):
                    combatArmor2 =  t.GetStat(self.DamageType2+"Armor")
                    combatResist2 =  t.GetStat(self.DamageType2+"Resist")
                if hasattr(t, 'Mobility'):
                    if (combatStat > t.Stats.Mobility):
                        critChance += combatStat - t.Stats.Mobility
                    elif (combatStat < t.Stats.Mobility):
                        missChance += t.Stats.Mobility - combatStat
                diceRoll = renpy.random.random()*100
                if diceRoll > 100 - critChance:
                    combatDamage = (self.AttackDamage * 2 * damageRate)-((self.AttackDamage * 2 *damageRate * combatResist) / 100)-combatArmor
                    if hasattr(self, 'DamageType2'):
                        combatDamage += (self.AttackDamage2 * 2 *damageRate)-((self.AttackDamage2 * 2 *damageRate * combatResist2) / 100)-combatArmor2
                    combatDamage = round(combatDamage-damageFalloff)
                    t.Damage(combatDamage, self)
                elif diceRoll > missChance:
                    combatDamage = (self.AttackDamage *damageRate)-((self.AttackDamage *damageRate * combatResist) / 100)-combatArmor
                    if hasattr(self, 'DamageType2'):
                        combatDamage += (self.AttackDamage2 *damageRate)-((self.AttackDamage2 *damageRate * combatResist2) / 100)-combatArmor2
                    combatDamage -= damageFalloff
                    combatDamage = round(combatDamage)
                    if combatDamage > 0:
                        t.Damage(combatDamage, self)
                else:
                    t.Damage(0, self)

Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#154 Post by Jake »

CaseyLoufek wrote:It works but unlike my previous method seems to be pulling Scenary which I hadn't yet designed to interact with my combat rules.
GetOccupants will definitely include scenery - it's also used for determining LoS, which Scenery is quite relevant for!

You can weed it out simply by checking isinstance(fighter, Scenery) - I know it's something you're discouraged from doing in Python because blah blah blah duck typing blah blah, but seriously: screw that. ;-)

(I'll add it to my todo list to add a named parameter to exclude Scenery from the search... but it'll just be doing the same filter behind the function call, of course.)
Server error: user 'Jake' not found

CaseyLoufek
Regular
Posts: 142
Joined: Sat May 28, 2011 1:15 am
Projects: Bliss Stage, Orbital Knights
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#155 Post by CaseyLoufek »

It works fine with the filter and I don't mind being able to get Scenary.

I do want to be able to damage the scenary but I hadn't programmed that yet and what's more the errors I was getting seem logically impossible. The values being multiplied couldn't be NoneTypes and certainly shouldn't be different because the subclass they are working with changed.

I was hoping you might have some clue as to the nature of the error. I am going to assume at this point that the debugger is just wrong. I've seen this a couple times before, Ren'py and/or the rest of Python get so confused that they don't actually understand what went wrong and report the wrong thing. I found out by fixing something completely unrelated and watching the inexplicable error disappear.

Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#156 Post by Jake »

CaseyLoufek wrote:
I was hoping you might have some clue as to the nature of the error. I am going to assume at this point that the debugger is just wrong. I've seen this a couple times before, Ren'py and/or the rest of Python get so confused that they don't actually understand what went wrong and report the wrong thing. I found out by fixing something completely unrelated and watching the inexplicable error disappear.


I've certainly seen the same thing - not just in Python, either, but in pretty much every programming langague I've used to any serious degree! Often a step-debugger is the best way to work out what actually happened... but unfortunately step-debugging Ren'Py games is one of the most tedious things in the world.

One thing that you can do is start at the point of the error and print out debug messages - say, write out the value of all of the variables that you're using in the line that breaks, just before that line. Then based on which one is causing the problem, work backwards from there to the places those variables get affected... and so on. I don't know if you're aware, but anything you print out of a Ren'Py game will come up in the console if you check the 'Console Output' preference (or run with the console executable in previous versions). IIRC it comes out to STDOUT in Windows and Linux. On OSX you're a little bit screwed at least in Snow Leopard, 'cause it goes to Console in Utilities, and that sometimes doesn't flush until Ren'Py actually closes... but maybe they've fixed that post-Snow-Leopard.

Two things come to mind, but no guarantees that either of them are your problem. Firstly, just because you're setting defaults for a parameter doesn't mean they can't be None - if a None is explicitly passed in that will still override the default. It doesn't look like it from your code snippet, but it's something to be aware of. Secondly, if you call GetStat to fetch a stat which the fighter doesn't actually possess, then that will return a None. On top of this, all the various skill/mechanic/etc. SetUpFighter methods are only called for Fighters, not for Scenery (look at the AddFighter and AddScenery methods in engine.rpy). So if you're relying on stats definitely being there because you added them in skill setups or the mechanic setup, that only counts for actual Fighters and not for Scenery. (Maybe this needs to be a configurable option in future versions of the engine, if people are going to want to have Scenery interacting in the same way Fighters do to damage and so on... I'm open to suggestions/requests.)
Server error: user 'Jake' not found

CaseyLoufek
Regular
Posts: 142
Joined: Sat May 28, 2011 1:15 am
Projects: Bliss Stage, Orbital Knights
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#157 Post by CaseyLoufek »

Jake wrote: Secondly, if you call GetStat to fetch a stat which the fighter doesn't actually possess, then that will return a None.
That would do it. I assumed the result would be an error. So the Attack winds up setting combatResist to None but doesn't complain when doing so. Makes sense now. :)

Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#158 Post by Jake »

CaseyLoufek wrote: I assumed the result would be an error.
I seem to recall that I thought that returning None would be more friendly for novice programmers... but I can't for the life of me think why, in retrospect!
Server error: user 'Jake' not found

CaseyLoufek
Regular
Posts: 142
Joined: Sat May 28, 2011 1:15 am
Projects: Bliss Stage, Orbital Knights
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#159 Post by CaseyLoufek »

0 would probably be the friendliest.

Edit: You know you're a programmer when the difference between Zero and None is important.

Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#160 Post by Jake »

CaseyLoufek wrote:0 would probably be the friendliest.
Possibly, it's certainly an option.

I've recently done a load of work to make sure that stats don't have to be numeric! ;-) I guess that even in cases where people are expecting text a 0 as a failure state isn't going to be a problem, though.
Server error: user 'Jake' not found

User avatar
Kokoro Hane
Eileen-Class Veteran
Posts: 1237
Joined: Thu Oct 27, 2011 6:51 pm
Completed: 30 Kilowatt Hours Left, The Only One Girl { First Quarter }, An Encounter ~In The Rain~, A Piece of Sweetness, Since When Did I Have a Combat Butler?!, Piece by Piece, +many more
Projects: Fateful Encounter, Operation: Magic Hero
Organization: Tofu Sheets Visual
Deviantart: kokoro-hane
itch: tofu-sheets-visual
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#161 Post by Kokoro Hane »

This looks a really good battle system and it's about time I start playing with it. I've always wanted to make an RPG, and to be able to implement it into Ren'Py? That's a plus!
PROJECTS:
Operation: Magic Hero [WiP]
Piece By Piece [COMPLETE][Spooktober VN '20]
RE/COUNT RE:VERSE [COMPLETE][RPG]
Since When Did I Have a Combat Butler?! [COMPLETE][NaNoRenO2020+]
Crystal Captor: Memory Chronicle Finale [COMPLETE][RPG][#1 in So Bad It's Good jam '17]

But dear God, You're the only North Star I would follow this far
Owl City "Galaxies"

User avatar
azureXtwilight
Megane Procrastinator
Posts: 4118
Joined: Fri Mar 28, 2008 4:54 am
Completed: Fantasia series (ROT and ROTA), Doppleganger: Dawn of The Inverted Soul, a2 (a due), Time Labyrinth
Projects: At Regime's End
Organization: Memento-Mori VNs, Team Sleepyhead
Location: Yogyakarta, Indonesia.
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#162 Post by azureXtwilight »

I am wondering if I can use the code for temporary increasing own skill to make a skill that decrease the enemy's skill? Which parts should be modified for it?
Image

Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#163 Post by Jake »

azureXtwilight wrote:I am wondering if I can use the code for temporary increasing own skill to make a skill that decrease the enemy's skill? Which parts should be modified for it?

The same kind of code should work fine for any kind of stat modification on anyone, really! You'll need to:

- modify the targeting data for the skill so that it targets enemies instead of friendlies
- change the effect so that the methods which modify the stat reduce instead of increase the stat

The main thing you need to bear in mind is the same as for positive stat modification: simply that whatever modification you do in OnRetrieveStat - which is the change that effect makes on the fighter's stat - needs to be absolutely reversed in InSetStat, to make sure that anything else that modifies a fighter's stats while the effect is in place acts in an appropriate manner. So if you're making a "half health plus a small extra" effect which is applied to enemies, then perhaps OnRetrieveStat does this:

Code: Select all

            if name == 'Health':
                return (value / 2) + 10;
            else:
                return value
in which case OnSetStat needs to do this:

Code: Select all

            if name == 'Health':
                return int((value - 10) * 2)
            else:
                return value
... because you also need to perform all the operations in the opposite order. Like this, if a fighter's on 100 health then OnRetrieveStat will report his health as (100/2)+10 = 60, and then if he gets attacked for 15 damage and his Health is set to (60 - 15) = 45, his actual base health (currently 100) will be set to (45 - 10) * 2 = 70. Then when displayed through OnRetrieveStat, (70 / 2) + 10 = 45, so it does indeed look like he's losing 15 health.

The difference between his original base health of 100 and his new base health of 70 is 30, which is twice the damage of 15 - correct, because making someone's health half what it normally is should be the same as making all damage twice what it normally is, in terms of how much damage it takes to kill them!

If the OnSetStat calculation had been int((value * 2) - 10), the other way around, then the end result would have been (45 * 2) - 10 = 80, which would then have been displayed through OnRetrieveStat as (80 / 2) + 10 = 50... so he would have been shown as only losing 10 health for a 15-damage attack!






(You'll probably also want to give those classes - the Skill and the Effect - completely new names, as well as changing the name the effect uses to apply itself to the fighter, of course; so they don't overwrite the classes you've previously defined for the stat-raising effect!)
Server error: user 'Jake' not found

User avatar
azureXtwilight
Megane Procrastinator
Posts: 4118
Joined: Fri Mar 28, 2008 4:54 am
Completed: Fantasia series (ROT and ROTA), Doppleganger: Dawn of The Inverted Soul, a2 (a due), Time Labyrinth
Projects: At Regime's End
Organization: Memento-Mori VNs, Team Sleepyhead
Location: Yogyakarta, Indonesia.
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#164 Post by azureXtwilight »

Okay, just managed to find the way to do it. You might want to include it in the next demo :D
I still can't find out how to make the enemy heals it's own ally, though.
This one's for reducing the enemy's defense

In engine-skills:

Code: Select all

class WeakSkill(Skill):
        
        def __init__(self, name="Weak--Weakens Enemy", command=None, hotkey='p', weight=0, turns=2):

            self._targets = TargetData(fighters=True, los=True, friendly=False, enemy=True, range=4)

            if command == None:
                command=[("Magic",5), (name,0)]
            
            super(WeakSkill, self).__init__(name=name, command=command, hotkey=hotkey, weight=weight)
            
            self._turns = turns
            
        def SetUpFighter(self, fighter):
            fighter.RegisterStat("MP", 20)
            
        def IsAvailable(self, fighter):
            if (fighter.Stats.MP >= 20):
                return True
            else:
                return False   
            
        def PerformAction(self, fighter, target):
            
            f = target[0]
            renpy.music.play("audio/Enemy/Imp2.mp3", channel="sound", loop=False) 
            
            face = fighter._battle.GetFacing(fighter.Position, target[0].Position)
            fighter.Facing = face
                    
            if (_preferences.battle_skip_incidental == False):
                fighter._battle.ChangeFighterState(fighter, "magic", facing=face)

            
            f.AddEffect("Weak", WeakEffect(f, self._turns))
            
            fighter._battle.ChangeFighterState(fighter, "default", facing=face)
            
            fighter.Stats.MP = fighter.Stats.MP - 20

            
            fighter.EndTurn()            
In engine-extras:

Code: Select all

class WeakEffect(Effect):
        
        def __init__(self, fighter, turns=2):
            # Do the default setup for an effect
            _battle.Announce(fighter.Name+"'s defense is reduced!")
            super(WeakEffect, self).__init__(fighter)
            
            # set up a starting turn-count for the haste effect to last
            self._turns = turns
            self._name = "Weak"
        
        def FighterStartTurn(self, fighter):
            
            # count down one turn at the beginning of every turn this fighter takes under this effect
            if (fighter == self._fighter and self._turns > 0):
                self._turns = self._turns - 1
            
        def FighterEndTurn(self, fighter):
            
            # If we've run out of turns, remove the effect so haste no longer applies
            if (fighter == self._fighter and self._turns == 0):
                fighter.RemoveEffect("Weak")
                _battle.Announce("Weak wears off for " + fighter.Name)
                self._turns = -1
            
        def OnRetrieveStat(self, name, value):
            if name == 'Defence':
                return value / 0.5
            else:
                return value
                
        def OnSetStat(self, name, value):
            if name == 'Defence':
                return int(value * 0.5)
            else:
                return value                        
Image

Jake
Support Hero
Posts: 3826
Joined: Sat Jun 17, 2006 7:28 pm
Contact:

Re: RPG Battle Engine - Alpha 7.5, downloads in first post

#165 Post by Jake »

azureXtwilight wrote: In engine-skills:
In engine-extras:
As a word of advice, it's generally a good idea to not modify the engine files at all, and just add those classes in new .rpy files in your project directory.

If you modify the engine files directly and I release a new version of the engine with some bugfixes you need, you'll have to spend a while making sure you don't overwrite any of your changes by accident! If you keep them in separate files, you can just copy the new engine files over the top of the old ones without much hassle.
Server error: user 'Jake' not found

Post Reply

Who is online

Users browsing this forum: No registered users