Player Perk Implementation [SOLVED]

Discuss how to use the Ren'Py engine to create visual novels and story-based games. New releases are announced in this section.
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
Message
Author
DapNix
Regular
Posts: 52
Joined: Fri Mar 01, 2013 6:15 am
Contact:

Player Perk Implementation [SOLVED]

#1 Post by DapNix » Wed Feb 25, 2015 2:17 pm

It's me again, the guy who's asked about 50 questions on menus and such the last few days. :) This looks like it'll be the last time though, because my special menu is almost done!
Last thing I need help with, is there a way to make a screen action that runs a function? I'll attach screenshots along with code that I've got to give you guys a more detailed view of how the entire thing looks!
Going into the game menu has an option called "Chrystel" that is unclickable until you've reached a certain point in the game. Once you've gained access to the Chrystel Menu, clicking the button will lead you to this screen:
Image
^The "Warrior Lv.1" text signifies the role you've chosen (You get to choose a role in my game, kinda like WoW-classes), "Algan Chrystel: 400" speaks for itself. Algan is the name of the main character and he has 400 chrystel that he can pay with. This screen is made up an imagemap where clicking an orb will take you to another screen. Currently, the only one clickable is the red one in the bottom. Once you've successfully spent the necessary chrystel to get that upgrade, the two orbs above it will become available and you kinda choose a path. I think it's cool, okay? :)
Now, if you click the red orb, you'll be brought to this screen:
Image
^Here, you will confirm purchasing the upgrade. The "Purchase" button is unclickable if you don't have enough chrystel to buy the upgrade, but for testing reasons I've currently set it so that it can be bought already. So here comes the tricky part, if I click that button, how do I make it run a function? I'll show involved code below, it's very possible that my coding is off, I'd appreciate if you give me tips on how to improve it.

Code: Select all

label start:
    #Create perks (name, description, effect, cp_cost=0)
    $Atk1 = Perk("Attack +20", "Increases attack by 20. \n\nCosts 500 Chrystel Points.", "Atk20", 500)
^Here's where I create the perk, nothing fancy.

Code: Select all

class Perk():
        def __init__(self, name, description, effect, cp_cost=0):
            self.name = name
            self.description = description
            self.effect = effect
            self.cp_cost = cp_cost

        def upgrade(self, cp_cost, effect):
            if perk.effect == Atk20:
                player.power += 20
                player.chrystel -= perk.cp_cost
^And this is the Perk class itself. I'm worried that my code is off on the "upgrade" definition, but I'm not sure.

Code: Select all

vbox align (.2, .47):
        textbutton "Purchase":
            action [Return (value=Atk1), SensitiveIf(player.chrystel>=400)]
^And lastly, this is the code I've got in my screen for the Purchase button. SO: If the player clicks the purchase button AND can afford the upgrade, returning the value of Atk1 WOULD work, I think. Basically I want the player to gain 20 attack and lose 500 chrystel if he clicks it.

That was a lot of text. So any ideas on how to do it? The reason as to why this doesn't work at the moment is because calling the Return in a menu screen simply takes you back to the game, regardless of what value I attach to it. Any help is appreciated!
Last edited by DapNix on Sun Mar 01, 2015 6:17 pm, edited 4 times in total.

philat
Eileen-Class Veteran
Posts: 1759
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: Player Perk Menu issues

#2 Post by philat » Wed Feb 25, 2015 3:09 pm

How to correctly implement the perk system another topic entirely, since I can't tell from your code how the perk system would interact with battles, etc., but why are you returning Atk1? How are you using the returned Perk?

For what it's worth, a screen action can be a function that doesn't take any arguments. http://www.renpy.org/doc/html/screen_actions.html

DapNix
Regular
Posts: 52
Joined: Fri Mar 01, 2013 6:15 am
Contact:

Re: Player Perk Implementation

#3 Post by DapNix » Wed Feb 25, 2015 4:01 pm

I figured you didn't need to know how the battle system works. I'll try to show some code:

Code: Select all

class Fighter(object):
        def __init__(self, name, roles=[], perks=[], image=None, status_avatar=None, max_hp=0, max_sp=0, max_mp=0, max_spirit=0, skills=[], level=1, power=0, defense=0, hit=0, evasion=0, speed=0, align_Evil=0, align_Good=0, items=[], money=0, chrystel=0, guard=0, bind=0, poisonedTurns=0, virulentPoison=0, stunnedTurns=0, annoy=0):
^That's the class for fighters. One fighter is "player", which is our main character. The definition "upgrade" in the Perk class said:

Code: Select all

if perk.effect == Atk20:
    player.power += 20
    player.chrystel -= perk.cp_cost
This is also why I decided to run value=Atk1, because I figured that if I tried to call Atk1 then the def upgrade should kick in and increase the players power by 20 and reduce the chrystel (income) by the amount the upgrade costed. Like I said I'm unsure of how the def upgrade will affect Atk1, that was just a wild guess.. :P it would be much easier to just run the definition as you would regularly by creating a

Code: Select all

def addPerk(self, perks):
    self.perks.append (Atk1)
in the fighter code and then just run that with a simple "player.addPerk()" or something like that...
For what it's worth, a screen action can be a function that doesn't take any arguments.
Not sure quite what you mean there..

philat
Eileen-Class Veteran
Posts: 1759
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: Player Perk Implementation

#4 Post by philat » Wed Feb 25, 2015 4:17 pm

I get the sense that you kind of misunderstand what "return" does. Return literally "returns" a value, i.e. if a function returns X, it's as if you asked the function hey give me X, and the function hands it to you -- but that doesn't mean you're DOING anything with the returned value until you specifically do something with it -- by passing it to a function or checking it with if conditions, etc.

You also don't "run" the class itself, you run methods associated with the class. In this case, you need to run Atk1.upgrade(), not Atk1 itself.

In your case, all the relevant code is only being run once -- it's not set up to be dynamic, like "if player has X, Y, and Z perks, every time the battle system is run, the system will see the perks and adjust accordingly"; it's static, like "when you get X perk, it will run the upgrade method and you will get a permanent increase in stats." In that case, A) I don't know that there's a huge benefit to even having a perk class as opposed to hard-coding the benefits once, although I suppose if you have multiples of the same perk at different levels or you want to set up the screens by iterating through the perks there could be some efficiencies there, and B) all you need to do is have the button run the upgrade method (which I haven't tried but as long as it doesn't take any arguments, it should work).

Disclaimer: All of the following is untested and meant to be a general concept rather than flawlessly working code ;)

Code: Select all

class Perk():
    def upgrade(self): # you don't use the provided cp_cost and effect variables anyway because they're already part of the Perk class
        if self.effect == "Atk20":
            player.power += 20
            player.chrystel -= self.cp_cost
            player.perks.append(self) # just to make sure you can only get the same perk once

screen:
    textbutton "blahblah" action [SensitiveIf(player.chrystel >= 400 and Atk1 not in player.perks), Atk1.upgrade] 
    # The above button runs Atk1.upgrade() if Atk1, which is the Perk you defined before, is not in player.perks. For whatever reason, when running functions as screen actions, you're supposed to leave out the parentheses. / I haven't tried using "not in" as the condition for a SensitiveIf action, but theoretically it should work? If not, you know, figure something else out either with SensitiveIf() or If().
ETA: if you're going to have a Perk class, I would suggest you tinker with it to optimize it a little -- for example, have it take the effected stat and effect number separately so you can change the upgrade method to something like

self.effected_stat += self.effect_amount (where self.effected_stat would be something like player.power and self.effect_amount would be the number)

rather than having a bunch of if self.effect == "blah", and so forth. But that's neither here nor there.

ETA2: arguments are what go inside the parentheses of a function (e.g. Jump("label") is the Jump function called with "label" as the argument). So besides the regular screen actions which are predefined, any function without arguments (such as renpy.restart_interaction(), which is one you'll see asked about a lot here ;) ) can be supplied as a screen action. Or you can define your own screen actions, but that's pretty advanced.

DapNix
Regular
Posts: 52
Joined: Fri Mar 01, 2013 6:15 am
Contact:

Re: Player Perk Implementation

#5 Post by DapNix » Wed Feb 25, 2015 4:47 pm

I'm just going to be completely honest here and say: Even if this solution doesn't work, I appreciate the fact that you just taught me a bunch of stuff. I'm still obviously in learning so any advice like this is strongly appreciated :) I was thinking of making a class the way you said it:
"if player has X, Y, and Z perks, every time the battle system is run, the system will see the perks and adjust accordingly"
but at the time when I planned on making this my knowledge of python was very limited so I created a very temporary solution at the time. It takes time to change around all the stuff I've done in my code but I realize that it'll become much more smooth if I do it more dynamically like that.
Once again, thanks a bunch, I'll take all of that to mind as well as I can :D

ETA: Mixing around with the stuff you sent me, I managed to to a temporary solution, huge thanks once again Philat! :D I'll be off to think about some smoother ways to implement it now that I have a basic understanding of how it works! Feels nice to know that I can run functions in screens :P

philat
Eileen-Class Veteran
Posts: 1759
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: Player Perk Implementation [SOLVED]

#6 Post by philat » Fri Feb 27, 2015 1:52 pm

Randomly jumping back in to say I don't think it's necessarily true that it would run "more smoothly" if you set it up to be dynamic. There's nothing wrong with keeping it simple if the gameplay isn't going to be extra complicated. The main advantage of having it be dynamic is that you can then have a bunch of perks to mix and match according to the battle (like you would equipment). It's the difference between single-use stat-raising items and stat-raising items like that you can equip but you only have two slots. And sure the latter sounds like a cool mechanic, but it's also kind of a headache to implement and balance test properly. So, you know. Just my two cents.

But anyway, glad I could help.

DapNix
Regular
Posts: 52
Joined: Fri Mar 01, 2013 6:15 am
Contact:

Re: Player Perk Implementation [SOLVED]

#7 Post by DapNix » Fri Feb 27, 2015 2:13 pm

No need to worry about that, I've added a variable to the perk class called "is_Stat", where the stats such as flat HP and power are labeled as is_Stat and are applied permanently, whereas other perks with gameplay-changing effects are simply applied as perks which are then checked later :)

DapNix
Regular
Posts: 52
Joined: Fri Mar 01, 2013 6:15 am
Contact:

Re: Player Perk Implementation [unsolved]

#8 Post by DapNix » Sun Mar 01, 2015 4:08 pm

Ran into an error and decided to update this one rather than make a new post.

Code: Select all

    class Perk():
        def __init__(self, name, description, cp_cost=0, is_Atk=0, is_Hp=0, is_Sp=0, is_critChance=0, is_Skill=0, is_Perk=0, effect=0, purchased=0):
            self.name = name
            self.description = description
            self.cp_cost = cp_cost
            self.is_Atk = is_Atk
            self.is_Hp = is_Hp
            self.is_Sp = is_Sp
            self.is_critChance = is_critChance
            self.is_Skill = is_Skill
            self.is_Perk = is_Perk
            self.effect = effect
            self.purchased = purchased

        def upgrade(self):
            player.chrystel -= self.cp_cost
            self.purchased += 1
            if self.is_Atk == 1:
                player.power += self.effect
            if self.is_Hp == 1:
                player.max_hp += self.effect
            if self.is_Sp == 1:
                player.max_sp += self.effect
            if self.is_critChance == 1:
                player.crit += selfeffect
            if self.is_Skill == 1:
                player.skills.append(self)
            if self.is_Perk == 1:
                player.perks.append(self)
^This is where we define perks

Code: Select all

$Atk1 = Perk("Attack +25", "Increases attack by 25. \n\nCosts 500 Chrystel Points.", 500, 1, 0, 0, 0, 0, 0, 25, 0)
^This is the one particular perk we will be looking at

Code: Select all

vbox align (.2, .47):
        textbutton "Purchase":
            action Atk1.upgrade, ShowMenu('chrystelAlgan')
^And this is within a screen statement. Running Atk1.upgrade increases the players stats by 25 as it should, however if I quit the game (or save game and then reload) the stats are lost. Why is this?
Could it have something to do with the fact that Fighters and Perks are defined in different classes? What can I do to fix it?

philat
Eileen-Class Veteran
Posts: 1759
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: Player Perk Implementation [unsolved]

#9 Post by philat » Sun Mar 01, 2015 4:23 pm

Sounds like you're creating the instances (player, Atk1) in an init block instead of after the start label, which can cause issues in rollback and save/load. It doesn't matter if you define the classes themselves in init, but when you actually create instances, those should generally be after the start label, as best practice.

DapNix
Regular
Posts: 52
Joined: Fri Mar 01, 2013 6:15 am
Contact:

Re: Player Perk Implementation [unsolved]

#10 Post by DapNix » Sun Mar 01, 2015 4:40 pm

All perks, fighters and skills are created in the start label, not an init: block.

Could the error lie in the fact that the perks are applied from a screen?

philat
Eileen-Class Veteran
Posts: 1759
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: Player Perk Implementation [unsolved]

#11 Post by philat » Sun Mar 01, 2015 4:44 pm

In that case, I'm not sure what's going on. For instance, the following simplified code will save/load normally.

ETA: was re-reading and realized your Fighter inherits from object, rather than store.object. I'm fuzzy on the details, but afaik this may also cause issues with save/load/rollback.

Code: Select all

init python:
    class Fighter(store.object):
        def __init__(self, name, power):
            self.name = name
            self.power = power

    class Perk(store.object):
        def __init__(self, name, power):
            self.name = name
            self.power = power
        def upgrade(self, player):
            player.power += self.power

#The game starts here.
label start:

    $ player = Fighter("Player 1", 10)
    $ Atk1 = Perk("Attack 1", 10)

    "Player power: [player.power]"

    $ Atk1.upgrade(player)

    "Player power upgrade: [player.power]"

    "save/load here"

    "Player power after load: [player.power]"
Last edited by philat on Sun Mar 01, 2015 4:55 pm, edited 1 time in total.

DapNix
Regular
Posts: 52
Joined: Fri Mar 01, 2013 6:15 am
Contact:

Re: Player Perk Implementation [unsolved]

#12 Post by DapNix » Sun Mar 01, 2015 4:54 pm

Yeah, I can run it like that too without any problems. For some reason it only screws up when I try to apply it from a screen. Just writing

Code: Select all

$Atk1.upgrade(player)
regularly in the code works fine. But in my screen where you buy perks you click a textbutton that does:

Code: Select all

    vbox align (.2, .47):
        textbutton "Purchase":
            action Atk1.upgrade(player), ShowMenu('chrystelAlgan')
for some reason it doesn't stay on the player when done like this.

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Player Perk Implementation [unsolved]

#13 Post by trooper6 » Sun Mar 01, 2015 5:07 pm

You know, I'm no expert but could the problem be that you aren't passing an object to update to your updater? Notice the difference between philat's perk code:

philat wrote:

Code: Select all

init python:
    class Perk(store.object):
        def upgrade(self, player):
            player.power += self.power
And this shortened version of your perk code:
DapNix wrote:

Code: Select all

    class Perk():
        def upgrade(self):
            player.chrystel -= self.cp_cost
            self.purchased += 1
            if self.is_Atk == 1:
                player.power += self.effect
            if self.is_Hp == 1:
                player.max_hp += self.effect
            if self.is_Sp == 1:
                player.max_sp += self.effect
            if self.is_critChance == 1:
                player.crit += selfeffect
            if self.is_Skill == 1:
                player.skills.append(self)
            if self.is_Perk == 1:
                player.perks.append(self)
You never pass it the player to upgrade and because it is in Python and a function it doesn't have access to the outside world unless you pass it along or, I think, make player global.
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

philat
Eileen-Class Veteran
Posts: 1759
Joined: Wed Dec 04, 2013 12:33 pm
Contact:

Re: Player Perk Implementation [unsolved]

#14 Post by philat » Sun Mar 01, 2015 5:13 pm

As far as I can tell, the following loads correctly as well.

ETA: trooper6, I think the reason for not having an argument there is because you can supply any function that doesn't take arguments as a screen action, but to take arguments you have to custom define your own screen actions. In any case, I don't think that's the issue, since the below works, at least it seems to for me.

Code: Select all

screen upgrade():
    textbutton "Upgrade" action [Atk1.upgrade, Hide("upgrade")]

init python:
    class Fighter(store.object):
        def __init__(self, name, power):
            self.name = name
            self.power = power

    class Perk(store.object):
        def __init__(self, name, power):
            self.name = name
            self.power = power
        def upgrade(self):
            player.power += self.power

#The game starts here.
label start:

    $ player = Fighter("Player 1", 10)
    $ Atk1 = Perk("Attack 1", 10)
    show screen upgrade

    "Player power: [player.power]"
    "Player power upgrade: [player.power]"
    "save/load here"
    "Player power after load: [player.power]"

User avatar
trooper6
Lemma-Class Veteran
Posts: 3712
Joined: Sat Jul 09, 2011 10:33 pm
Projects: A Close Shave
Location: Medford, MA
Contact:

Re: Player Perk Implementation [unsolved]

#15 Post by trooper6 » Sun Mar 01, 2015 5:28 pm

I think yours works because of the store.Object. Without it, the function is not going to be able to get access to the right "player" variable, and will probably just make up its own that doesn't exist outside of that function. My understanding of how the namespace thing works without passing the variables is that you either have to do:

Code: Select all

    class Perk(store.object):
        def __init__(self, name, power):
            self.name = name
            self.power = power
        def upgrade(self):
            player.power += self.power
or

Code: Select all

    class Perk():
        global player
        def __init__(self, name, power):
            self.name = name
            self.power = power
        def upgrade(self):
            player.power += self.power
The OP did neither.
A Close Shave:
*Last Thing Done (Aug 17): Finished coding emotions and camera for 4/10 main labels.
*Currently Doing: Coding of emotions and camera for the labels--On 5/10
*First Next thing to do: Code in all CG and special animation stuff
*Next Next thing to do: Set up film animation
*Other Thing to Do: Do SFX and Score (maybe think about eye blinks?)
Check out My Clock Cookbook Recipe: http://lemmasoft.renai.us/forums/viewto ... 51&t=21978

Post Reply

Who is online

Users browsing this forum: Archon, EightiesOne