Requesting feedback regarding script

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.
Post Reply
Message
Author
Eiliya
Regular
Posts: 148
Joined: Tue Dec 04, 2012 6:21 am
Contact:

Requesting feedback regarding script

#1 Post by Eiliya »

As I mentioned yesterday, I've been trying to make a combat script for a game I'm developing. This is what my script looks like at this moment (bottom of the post). If people have the time, I'd like some feedback with regards to how it's written. It's quite long, so sorry about that.

Here's a quick summary of how I want it to work.

First, the normal game sets the various stats and all that, and then it calls the atg_check label. This stacks the participants speed onto itself until they reach 255 (or above), at which point the combatant gets to act. The faster you are, the more frequently you get to act. Once it determines who gets to act, the atg of that participant is reset and the combatant is given action point they can use to perform actions during their turn. For now, I've only included the first slot on the player's side of the combat, but it'll all work in more or less the same way. At this moment, I've only coded the basic attack, which starts by asking the player to select a target and then generates an attack value (for the actor) and a defense value (for the defender). The defense is then subtracted from the attack, and the result is used as a % value when determining the amount of damage inflicted.

For example, if the attack has a final score of 50, the attack will deal 50% of the base damage (p1_dmg in the code below).

Any thoughts on how (if at all) I can improve this, information regarding how to turn it into an image map, or just general feedback would be greatly appreciated. Thank you very much for your time.

Code: Select all


label atg_check:        #this label determines the order in which the combatants act in combat
    if p1_atg > 254:
        $ p1_atg = 0
        $ p1_act = p01_act    #p01 is the MC of the game, this ensures the number of action points resets prior to starting the turn
        call p1_turn
    else:
        $ p1_atg += p1_spd
        call atg_check
    return

label p1_turn:            #this is the player menu - I wanna make it an image map, but soo complex T_T
    $ actor = 1
    if p1_act < 1:
        call atg_check
    menu:
        "It's the player's turn."
        "Attack":
            $ p1_dmg = 50
            menu:
                "Select target."
                "Enemy A":
                    $ target = 4
            call attack
            call defense
            $ attack -= defense
            "=Final Attack = [attack]="
            call damage
            call health
            $ p1_act -= 1
            call p1_turn
    return

label health:            #this label deals with inflicting the damage to the proper target
    if target == 1:
        $ p1_hps -= damage
    elif target == 2:
        $ p2_hps -= damage
    elif target == 3:
        $ p3_hps -= damage
    elif target == 4:
        $ e1_hps -= damage
    elif target == 5:
        $ e2_hps -= damage
    elif target == 6:
        $ e3_hps -= damage
    return

label damage:            #this label calculated the damage value (can this be rounded up or down to the nearest whole number?)
    $ damage = 0
    if attack < -40:     # the actor fumbles, hurting themselves instead
        $ target = actor
        $ attack = attack * (-1)
    elif attack < 40:    #the attack misses, dealing no damage
        $ attack = 0
    else:                    #the attack is successful
        pass
    if actor == 1:
        $ damage = p1_dmg * (attack/100)
    elif actor == 2:
        $ damage = p2_dmg * (attack/100)
    elif actor == 3:
        $ damage = p3_dmg * (attack/100)
    elif actor == 4:
        $ damage = e1_dmg * (attack/100)
    elif actor == 5:
        $ damage = e2_dmg * (attack/100)
    elif actor == 6:
        $ damage = e3_dmg * (attack/100)
    return

label defense:            #this is the defense calculation, used to determine how successful the target is at defending themselves
    call dice
    $ defense = dice
    if target == 1:
        $ defense += p1_def
        if defense > 400 and p1_lvl < 75:
            $ defense = 400
        if defense > 320 and p1_lvl < 50:
            $ defense = 320
    elif target == 2:
        $ defense += p2_def
        if defense > 400 and p2_lvl < 75:
            $ defense = 400
        if defense > 320 and p2_lvl < 50:
            $ defense = 320
    elif target == 3:
        $ defense += p3_def
        if defense > 400 and p3_lvl < 75:
            $ defense = 400
        if defense > 320 and p3_lvl < 50:
            $ defense = 320
    elif target == 4:
        $ defense += e1_def
        if defense > 400 and e1_lvl < 75:
            $ defense = 400
        if defense > 320 and e1_lvl < 50:
            $ defense = 320
    elif target == 5:
        $ defense += e2_def
        if defense > 400 and e2_lvl < 75:
            $ defense = 400
        if defense > 320 and e2_lvl < 50:
            $ defense = 320
    else:
        $ defense += e3_def
        if defense > 400 and e3_lvl < 75:
            $ defense = 400
        if defense > 320 and e3_lvl < 50:
            $ defense = 320
    return

label attack:            #This is the actual attack calculation, used to determine the success of the attack.
    call dice
    $ attack = dice
    if actor == 1:
        $ attack += p1_att
        if attack > 400 and p1_lvl < 75:
            $ attack = 400
        if attack > 320 and p1_lvl < 50:
            $ attack = 320
    elif actor == 2:
        $ attack += p2_att
        if attack > 400 and p2_lvl < 75:
            $ attack = 400
        if attack > 320 and p2_lvl < 50:
            $ attack = 320
    elif actor == 3:
        $ attack += p3_att
        if attack > 400 and p3_lvl < 75:
            $ attack = 400
        if attack > 320 and p3_lvl < 50:
            $ attack = 320
    elif actor == 4:
        $ attack += e1_att
        if attack > 400 and e1_lvl < 75:
            $ attack = 400
        if attack > 320 and e1_lvl < 50:
            $ attack = 320
    elif actor == 5:
        $ attack += e2_att
        if attack > 400 and e2_lvl < 75:
            $ attack = 400
        if attack > 320 and e2_lvl < 50:
            $ attack = 320
    else:
        $ attack += e3_att
        if attack > 400 and e3_lvl < 75:
            $ attack = 400
        if attack > 320 and e3_lvl < 50:
            $ attack = 320
    return

label dice:            #This generates a value, with limited criticals and fumbles, based on a typical d100.
    $ dice = 0
    call d100
    if d100 < 3:
        call d100
        $ dice -= d100
    elif d100 > 90:
        $ dice += d100
        call d100
        if d100 > 91:
            $ dice += d100
            call d100
            if d100 > 93:
                $ dice += d100
                call d100
                if d100 > 95:
                    $ dice += d100
                    call d100
                    if d100 > 97:
                        $ dice += d100
                        call d100
                        if d100 > 99:
                            $ dice += d100
    else:
        $ dice += d100
    return

label d100:         #This is the actual hundred-sided dice.
    $ d100 = renpy.random.randint(1,100)
    return


Aduckaducka
Newbie
Posts: 3
Joined: Fri Jan 15, 2016 1:26 am
Contact:

Re: Requesting feedback regarding script

#2 Post by Aduckaducka »

The current script has lots of duplicated code. This means whenever you want to change something (say, how attack value is calculated) you will have to change it for each duplicated piece of code. This takes a long time and is prone to errors.

You probably want to use object orientated programming -- it will make life a lot easier for you down the line. A good tutorial for your case is

http://inventwithpython.com/blog/2014/1 ... e-example/

To take advantage of object orientated programming you'll want to code most of the game machinery in pure python. This is easy through "python:" and "init python:" blocks. The battle code for the rpg I'm working on right now is almost entirely in pure python.

Eiliya
Regular
Posts: 148
Joined: Tue Dec 04, 2012 6:21 am
Contact:

Re: Requesting feedback regarding script

#3 Post by Eiliya »

I appreciate that you are trying to help me, but your comment doesn't really make all that much sense to me. You start of by saying that I use a lot of duplicated code. White it is true that I call a lot of the same label and use the same variables a lot, each individual situation has different criteria.

For example, one could say that the attack label uses a lot of the same code structure and seem to repeat a lot of the code. But in my defense, since I want to use the code in a universal manner, I need to tell the code how to differentiate between the various combatants that might be performing the attack.

Since there can be at most 3 player characters in the combat party and at most 3 monsters on the enemy side, this gives us a set of 6 possibilities as to who is performing the attack. Even if I use a class for this, wouldn't I still need to use six different strings of code? These's no guarantee that all combatants have the same attack value, so I'm not quite sure what you're getting at.

Although I am quite good at math and making strings and formulaes, when it comes to coding, I'm a complete beginner, so please forgive me if I ask obvious questions.

User avatar
mobychan
Veteran
Posts: 275
Joined: Fri Apr 24, 2015 6:31 am
Projects: The Chosen - Sakura Pink & Gentian Blue
Organization: Foresoft
Location: Germany
Contact:

Re: Requesting feedback regarding script

#4 Post by mobychan »

this is definitely easier to do using OOP.
An example how you could achieve your situation:

you create a class for "Actors"
you give that class different variables like "attack", "defense", whatever you need.
to calculate how much damage is dealt you create a method, that uses your "attack", "defense" or whatever variables to calculate the damage and return that value.

using this you can create all you "actors" using different values and still only need the code for damage calculations once

Tale a look at OOP, try to create the class/es you need and if it doesn't work, just come back, we'll be happy to help^^

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Requesting feedback regarding script

#5 Post by xela »

Eiliya wrote:I'm a complete beginner, so please forgive me if I ask obvious questions.
The code is fine for a beginner but if you're planning to keep developing games or writing code in general, you just learn OOP, there are no ifs/buts/pardons. As a general rule, any code bits that looks alike or do the same thing through a logical chain are considered novice and therefor can be improved upon.

This:

Code: Select all

         if d100 > 91:
            $ dice += d100
            call d100
            if d100 > 93:
                $ dice += d100
                call d100
                if d100 > 95:
                    $ dice += d100
                    call d100
                    if d100 > 97:
                        $ dice += d100
                        call d100
                        if d100 > 99:
                            $ dice += d100
should be a python for loop.

Code: Select all

label d100:         #This is the actual hundred-sided dice.
    $ d100 = renpy.random.randint(1,100)
    return
should be a function.

Code: Select all

    if actor == 1:
        $ attack += p1_att
        if attack > 400 and p1_lvl < 75:
            $ attack = 400
        if attack > 320 and p1_lvl < 50:
            $ attack = 320
    elif actor == 2:
        $ attack += p2_att
        if attack > 400 and p2_lvl < 75:
            $ attack = 400
        if attack > 320 and p2_lvl < 50:
            $ attack = 320
        ...
should be a function and your actors should be instances of a class carrying the require info. Maybe a battle engine should be a class as well, that depends on how complex you want it to be in the end.

===>
Learning/understanding basics of OOP will take time, so only you can make a call if it's worth an effort or just hack through any version of working code you can come up with.

===>
Any thoughts on how (if at all) I can improve this, information regarding how to turn it into an image map, or just general feedback would be greatly appreciated. Thank you very much for your time.
If this is the whole code that you're planning, improving might not be worth the effort. Turning into image map... I guess you just call a screen with an imagemap instead of a menu. Imagemap also suggests that you're only planning fights between the same six actors (which is rarely a case with BEs) so it's another pro in favor of not bothering with OOP...

I have a BE of my own and it's also 3vs3 but it'll be of no use as an example because it's too complicated (instance chars, sfx/gfx, elemental alignments, many skills, battle log, option of logical battle (just the combat calculations) and etc.) :( Prolly same thing counts for Jake's BE (google it if you haven't seen it already, it's pretty great). As I've said before, if you're planning more logic or using any amount of characters or special effects, you'll have to use OPP or by the time you get to something like this, your code will be impossible to manage.

There are some other examples in cookbook section but Jake's is the best (with intensely complicated and absurdly intertwined code as a tradeoff).
Last edited by xela on Thu Apr 07, 2016 9:23 am, edited 1 time in total.
Like what we're doing? Support us at:
Image

Eiliya
Regular
Posts: 148
Joined: Tue Dec 04, 2012 6:21 am
Contact:

Re: Requesting feedback regarding script

#6 Post by Eiliya »

I did take a look at the blog post that was given. That's why I know the contents make no sense to me. Let me start from the beginning then, with the most basic of backbones for my system, the creature stats. At the current moment, my code for the stats looks like this:

Code: Select all


init:
#Unspecified Values
    $ p1 = 0
#First Slot for Player Combatants
    $ p1_lvl = 0
    $ p1_hps = 0
    $ p1_mks = 0
    $ p1_kps = 0
    $ p1_kac = 0
    $ p1_att = 0
    $ p1_def = 0
    $ p1_dmg = 0
    $ p1_phr = 0
    $ p1_mar = 0
    $ p1_spd = 0
    $ p1_atg = 0
    $ p1_act = 0
#Noir - The Main Character
    $ p01_lvl = 2
    $ p01_exp = 0
    $ p01_hps = 145
    $ p01_mks = 120
    $ p01_kps = 50
    $ p01_kac = 8
    $ p01_att = 57
    $ p01_def = 50
    $ p01_dmg = 55
    $ p01_phr = 45
    $ p01_mar = 45
    $ p01_spd = 65
    $ p01_act = 3

label combat_setup:
    if p1 == 1: #Noir
        $ p1_lvl = p01_hps
        $ p1_hps = p01_hps
        $ p1_mks = p01_mks
        $ p1_kps = p01_kps
        $ p1_kac = p01_kac
        $ p1_att = p01_att
        $ p1_def = p01_def
        $ p1_dmg = p01_dmg
        $ p1_phr = p01_phr
        $ p1_mar = p01_mar
        $ p1_spd = p01_spd
        $ p1_act = p01_act
Of course, I've cut away all the code that isn't related to p1 and that isn't the character Noir, in an effort to make the way too long code a bit more readable here on the forums. Now, from what I understand about classes, I should be able to redo this into something like this:

Code: Select all

class Creature():
    def __init__(name, lvl, exp, hps, mks, kps, kac, att, def, dmg, phr, mar, spd, atg, act):
        self.name = name
        self.lvl = lvl
        self.exp = exp
        self.hps = hps
        self.mks = mks
        self.kps = kps
        self.kac = kac
        self.att = att
        self.def = def
        self.dmg = dmg
        self.phr = phr
        self.mar = mar
        self.spd = spd
        self.atg = atg
        self.act = act
This would then be usable for each combatant in a manner like this:

Code: Select all

player = Creature('Noir', 2, 0, 145, 120, 50, 8, 57, 50, 55, 45, 45, 65, 0, 3):
I'm also gussing that the monsters = [] is the base for some kind of list that then contains all the monsters, which in my case would look something like this:

Code: Select all

monsters = []
monsters.append(Creature('Abyssal Ape', 1, 100, 60, 0, 0, 0, 40, 60, 45, 60, 20, 80, 0, 1))
monsters.append(Creature('Shardspike Demon', 1, 120 , 0, 0, 0, 60, 40, 45, 60, 20, 40, 0, 1))
monsters.append(Creature('Darkmaw Shade', 2, 150, 320, 0, 30, 5, 80, 80, 60, 80, 40, 60, 0, 2))
I included the values that aren't used by the monsters, such as mks (this is used by the player to learn new abilities or upgrade old ones), because if I just skipped them, I'm guessing the order of the numbers would get messed up.

I also understand that by stating player = Creature('Noir', 2, 0, 145, 120, 50, 8, 57, 50, 55, 45, 45, 65, 0, 3) I can define that the player is Noir and set the correct stats, just as monsters = Creature('Abyssal Ape', 1, 100, 60, 0, 0, 0, 40, 60, 45, 60, 20, 80, 0, 1) shuld define the enemy as a single Abyssal Ape and set the correct stats for that. I understand all of this in theory, but I don't understand how to actually apply it. Where do I put the code for making classes and methods, how do I call to them from regular Renpy land?

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Requesting feedback regarding script

#7 Post by xela »

General rule is:

Code: Select all

init python:
    class Actor(object):
        ...

default actors = {}

label start:
    $ actor = Actor("Player", ...)
    $ actors[actor.name] = actor
    $ actor = Actor("Goblin Shaman", ...)
    $ actors[actor.name] = actor
    ...
Now you have this:

{"Player": object_with_players_data, "Goblin Shaman": object_with_goblins_data, ...}

You get easy access any data from the dict. I usually use JSON format to create data and a function to load it into dict, code is very clean that way.

You use it as you use any variables in the game, you just need to think in terms of objects and actions you wish to do with those objects:

Code: Select all

    if actor == 1:
        $ attack += p1_att
        if attack > 400 and p1_lvl < 75:
            $ attack = 400
        if attack > 320 and p1_lvl < 50:
            $ attack = 320
    elif actor == 2:
        $ attack += p2_att
        if attack > 400 and p2_lvl < 75:
            $ attack = 400
        if attack > 320 and p2_lvl < 50:
            $ attack = 320
        ...
Instead of linear code like this, you do something like:

Code: Select all

def attack(attacker, defender)
    attack = dice() + attacker.att # if dice is a func
    if attack > x and attacker.lvl < 75:
        attack = 400
    elif attack > y and p1_lvl < 50:
        attack = 320
    # I don't know if you get 400 and 320 from attacker or defender.
    return attack
and you're done. That 6 way fork is now one small function and you have a lot more possibilities while loosing nothing.
Like what we're doing? Support us at:
Image

Eiliya
Regular
Posts: 148
Joined: Tue Dec 04, 2012 6:21 am
Contact:

Re: Requesting feedback regarding script

#8 Post by Eiliya »

xela wrote:You just learn OOP, there are no ifs/buts/pardons.
I have no interest in making excuses of any kind. I wish to learn, I just lack the required know-how for it at the moment. Which is why I am here asking questions. And rather than read a wall of text that contains lingo that makes no sense to me (like that blog), I prefer to have people whom I can actually ask if there is something I don't understand. Hence why I go to this forum rather than watch vids on youtube or read tutorials on the net.
xela wrote:This:

Code: Select all

*snip*
should be a python for loop.
While I am sure that making a python for loop would be the ideal way to go here, I don't even know what that is, let alone how to make one. I added it to my to-search-for list, so once I've finished typing this reply I'll google for it and see if I can find anything, but if you don't mind, I'd greatly appreciate it if you could type one up for me here and give me a (preferably newbie-friendly) description of what does what within it.
xela wrote:

Code: Select all

*snip
should be a function.
Same as the one above. While I understand that a function is something that you call from one place in the code, I don't really understand how calling a function is different from calling a label. I also don't really know how to make a function, but it's been added to my google list.
xela wrote:

Code: Select all

*snip
should be a function and your actors should be instances of a class carrying the require info.
Yet again, I don't understand how this function you are talking about works. I did a little bit of simple searching in the middle of typing this, and figured out that a function is a block of reusable code that performs an action (or something like that). Although I am sure the professional way (which is what I am looking for, so please help me get educated) is the function, I just don't understand how this is different from just calling a label.

For example, if I make a label that generates a random number between 1-100 and then call that when I need to generate a random number, or if I create a user-defined function and then call that instead, except that it looks better in the code, what are the actual benefits?
xela wrote:Only you can make a call if it's worth an effort.
Of course it is. Although I love the Renpy engine, if I don't learn proper coding, I'll be severely limited in what I can actually do with the engine. It's absolutely worth the effort, I'm just having trouble making sense of the information I'm given. Which is why I am here asking for help.

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Requesting feedback regarding script

#9 Post by xela »

I don't even know what that is, let alone how to make one

Code: Select all

label dice:            #This generates a value, with limited criticals and fumbles, based on a typical d100.
    $ dice = 0
    call d100
    if d100 < 3:
        call d100
        $ dice -= d100
    elif d100 > 90:
        $ dice += d100
        call d100
        if d100 > 91:
            $ dice += d100
            call d100
            if d100 > 93:
                $ dice += d100
                call d100
                if d100 > 95:
                    $ dice += d100
                    call d100
                    if d100 > 97:
                        $ dice += d100
                        call d100
                        if d100 > 99:
                            $ dice += d100
    else:
        $ dice += d100
    return
instead:

Code: Select all

init python:
    def dice():
        return renpy.random.randint(1, 100)

Code: Select all

init python:
    def critical_dice():
        # This generates a value, with limited criticals and fumbles, based on a typical d100.
        result = 0
        d = dice()

        if d < 3:
            result = d - dice()
        else:
             result += d
             if d > 90:
                 for i in [91, 93, 95, 97, 99]:
                     d = dice()
                     if d > i:
                         result += d
                     else:
                         break

        return result
It should do the same thing, I haven't checked or tested but at the end of the day, you need more arsenal like for/while loops, lists, comprehensions and etc. to write code that does complex stuff with minimum effort.


===>
As a rule, you generally learn basic Python first, from Code Academy or Tutorials Point or something like that. Then read through Ren'Py Docs and maybe even glance through Python's docs really fast (they are huge but you should at least know all modules that may be of use to you at some point). Asking these questions one by one is prolly not productive cause they're all answered at those sites.
Like what we're doing? Support us at:
Image

Eiliya
Regular
Posts: 148
Joined: Tue Dec 04, 2012 6:21 am
Contact:

Re: Requesting feedback regarding script

#10 Post by Eiliya »

Let's see if I got this right...

I start by placing the init python: statement in much the same way as I do with a regular init: statement (what's the difference between these two, by the way?), and then define the call within that. Then I create an empty list of actors in the form of default actors = {}, which can then be filled with the relevant actors through the statements mentioned below the start label.

So if I were to make this in my own code, it would look something like this?

Code: Select all


init python:
    class Creature(object):    #why is there object within the ()?
        def __init__(name, . . . act):
            self.name = name
            .
            .
            .
            self.act = act

default actors = {}

label start:
    #lots of events leading up to the apes attacking through the door
    $ actor = Creature("Noir" . . . 3)
    $ actors[actor.Noir] = actor
    $ actor = Creature("Abyssal Ape" . . . 1)
    $ actors[actor.Ape1] = actor
    $ actor = Creature("Abyssal Ape" . . . 1)
    $ actors[actor.Ape2] = actor
    ...
And this would then set the proper stats for the various actors based on the values defined in the script according to the Creature class and then fill the actors into the empty list? If I'm misunderstood anything, please let me know :-)
xela wrote:I usually use JSON format to create data and a function to load it into dict, code is very clean that way.
And what is the JSON format, if I may be so stupid as to ask?

As for the attack and defense values, here's how I want it to work. The attacker performs the attack while the defender performs the defend (obviously). These two numbers are generated in exactly the same way, except that the non-random number used for them is different (att for the attacker and def for the defender). You start by generating the result of the random value, which you mentioned previously should be done by a python for loop. There needs to be two random numbers generated, one for the attacker and one for the defender. Onto these values we then add the att and def respectively, before we perform the final check to see if any of these reach the upper limit.

Once we have the final results for these values figured out, we compare them to see if the attack is a hit or not, as well as how much damage it inflicts. I took the code you provided and did some modifications according to how I figure it might work, but if this if flawed, please let me know how and why.

Code: Select all

def result(attacker, defender)
    attack = dice() + attacker.att # if dice is a func
    if attack > x and attacker.lvl < 75:
        attack = 400
    elif attack > y and p1_lvl < 50:
        attack = 320
    defend = dice() + defender.def
    if defend > x and defender.lvl < 75:
        defend = 400
    elif defend > y and defender < 50:
        defend = 320
    result = attack - defend
    return result

Eiliya
Regular
Posts: 148
Joined: Tue Dec 04, 2012 6:21 am
Contact:

Re: Requesting feedback regarding script

#11 Post by Eiliya »

xela wrote:As a rule, you generally learn basic Python first, from Code Academy or Tutorials Point or something like that. Then read through Ren'Py Docs and maybe even glance through Python's docs really fast.
I think I mentioned it before, but doing things that way is completely meaningless for me. Even when faced with the most basic of python, I get stuck on things that I do not understand and have to ask someone to provide a more easy to understand explanation. For example, I went to Tutorials Point to check for the Python for Loop that you mentioned, and was faced with a wall of text containing words I didn't understand, code that make no sense to me, code that contains functions I don't know why they are there and strange lists and diagrams.

And then we have the code you showed (I took the freedom to modify it a little).

Code: Select all


init python:
    def dice():
        return renpy.random.randint(1, 100)

    def critical_dice():
        result = 0
        d = dice()

        if d < 3:
            result = d - dice()
        else:
             result += d
             if d > 90:
                 for i in [91, 93, 95, 97, 99]:
                     d = dice()
                     if d > i:
                         result += d
                     else:
                         break
        return result
Now this. This makes sense. First we have a function dice which returns the result of the renpy.random.randint function. Then we have the critical_dice which starts by creating the variable result and setting it to 0. It then creates the variable d and connects it to the dice function, which gives d a value between 1 and 100. Then it checks if that value is below 3, which gives the result of d - dice (I'm guessing a new dice is generated here, since the dice() function is mentioned/called). If the result is something else (above 2, in other words), the function then adds the value from d to result, and then checks to see if d is above 90.

Now this is where things get messy for me. I know that the for i in [numbers] means that the code checks for these numbers when making the loop, but I don't understand what the i or the in stands for. Judging from the rest of the code, I'm assuming the i is a new variable that changes it's value (which is taken from the string within the []) each time it loops, starting at the left number and moving towards the right, but I'm not quite sure what part of the code actually makes it loop.

Anyways, I am too tired to properly think on this matter any further tonight, so I will end it with this post. When I return tomorrow, I'll look over any possible replies, and then shuffle around a bit on Tutorial Point trying to make some sense of things over there, but I'm quite sure I'll come back here to ask for further information and clarification in the end. Thanks for all the help thus far, everyone, and I wish you all a good night.

User avatar
xela
Lemma-Class Veteran
Posts: 2481
Joined: Sun Sep 18, 2011 10:13 am
Contact:

Re: Requesting feedback regarding script

#12 Post by xela »

Eiliya wrote:Let's see if I got this right...

I start by placing the init python: statement in much the same way as I do with a regular init: statement (what's the difference between these two, by the way?), and then define the call within that. Then I create an empty list of actors in the form of default actors = {}, which can then be filled with the relevant actors through the statements mentioned below the start label.

So if I were to make this in my own code, it would look something like this?

Code: Select all


init python:
    class Creature(object):    #why is there object within the ()?
        def __init__(name, . . . act):
            self.name = name
            .
            .
            .
            self.act = act

default actors = {}

label start:
    #lots of events leading up to the apes attacking through the door
    $ actor = Creature("Noir" . . . 3)
    $ actors[actor.Noir] = actor
    $ actor = Creature("Abyssal Ape" . . . 1)
    $ actors[actor.Ape1] = actor
    $ actor = Creature("Abyssal Ape" . . . 1)
    $ actors[actor.Ape2] = actor
    ...
And this would then set the proper stats for the various actors based on the values defined in the script according to the Creature class and then fill the actors into the empty list? If I'm misunderstood anything, please let me know :-)
init is what you can use with Ren'Py statements of any kind (image declarations, screens, styles, constants and etc.). init code will be ran every time the engine is started/rebooted. You can read more about it in the Docs. The part you wrote after label start is different from the example in my code and is likely to crash your game.

Code: Select all

python:
    ...
or

Code: Select all

$ ...
for one liners expects Python statements.

I don't know what you mean by "define a call within that". You don't create a list, it's called a dictionary, but you can use a list of you please. I would also like to point out that it is a good form not to use abbreviations for attributes, "attack" should be called attack. It's more of a general guideline than a rule but you'll be better off doing that in the long run. It's ok to abbreviate really long and complex names of attributes or variables, but you still want to leave a comment at the original declaration.

Code: Select all

MyClass(object):
ensures that new style classes are used (might be a case with Ren'Py by default, I don't remember). It will never hurt you or your code and will make sure that the game is functioning properly and that you can use all the features of Ren'Py and new style Python classes. Basically you just put it there even if you don't understand why.

Eiliya wrote:
xela wrote:I usually use JSON format to create data and a function to load it into dict, code is very clean that way.
And what is the JSON format, if I may be so stupid as to ask?
https://en.wikipedia.org/wiki/JSON

Don't bother with it for now, we allow hundreds of characters, items and etc. in our game so we're using it, you should be ok with just a few chars, keep it in the back of your mind as a simple, convenient way to store/prepare data for your game. This is an example of (part) of a file we use:

Code: Select all

[
    {
    "id": "Leather Cape",
    "desc": "A humble cloak of oily leather, well suited to the rigors of the road.",
    "icon": "content/items/cape/lc.png",
    "price": 30,
    "sex": "unisex",
    "chance": 80,
    "badness": 80,
    "eqchance": 10,
    "hidden": false,
    "infinite": true,
    "slot": "cape",
    "type": "armor",
    "max": {"defence": 5},
    "mod": {"defence": 5},
    "locations": ["Work Shop", "Look around"]
    },
    {
    "id": "Mantle of the Healer",
    "desc": "These mantles are mainly used by acolyte healers. They provide additional spirit energy for good deeds, and they are heavy as hell.",
    "icon": "content/items/cape/mh.png",
    "price": 140,
    "sex": "unisex",
    "chance": 70,
    "badness": 75,
    "eqchance": 11,
    "hidden": false,
    "infinite": false,
    "slot": "cape",
    "type": "other",
    "max": {"mp": 30, "vitality": -20, "agility": -10},
    "locations": ["Tailor Store", "Exploration"]
    },
    {
    "id": "Mantle of the Keeper",
    "desc": "It's no easy thing to see a Keeper, especially one who does not wish to be seen.",
    "icon": "content/items/cape/mk.png",
    "price": 4000,
    "sex": "unisex",
    "chance": 1,
    "badness": 0,
    "eqchance": 100,
    "hidden": true,
    "infinite": false,
    "slot": "cape",
    "type": "other",
    "max": {"mp": 200, "magic": 70, "agility": 30, "luck": 20, "intelligence": 15, "health": 20},
    "mod": {"magic": 100, "agility": 40, "luck": 30, "intelligence": 20},
    "locations": ["Exploration"],
    "goodtraits": ["Dandere"]
    },
    {
    "id": "Battle Cape",
    "desc": "This cape was reinforced by magic to provide advantages in battle.",
    "icon": "content/items/cape/bc.png",
    "price": 150,
    "sex": "unisex",
    "chance": 60,
    "badness": 75,
    "eqchance": 15,
    "hidden": false,
    "infinite": false,
    "slot": "cape",
    "type": "armor",
    "max": {"defence": 25},
    "mod": {"defence": 40},
    "mod_skills": {"refinement": [-0.01, 0, -0.02, 0, 0], "service": [-0.01, 0, -0.01, 0, 0]},
    "locations": ["Work Shop"]
    },
    {
    "id": "Elven Cape",
    "desc": "Comfortable and noiseless cape from an unknown, soft material.",
    "icon": "content/items/cape/ec.png",
    "price": 400,
    "sex": "unisex",
    "chance": 30,
    "badness": 20,
    "eqchance": 90,
    "hidden": false,
    "infinite": false,
    "slot": "cape",
    "type": "armor",
    "max": {"defence": 15, "agility": 15},
    "mod": {"defence": 35, "agility": 20},
    "mod_skills": {"refinement": [0, 0, 0.02, 0, 0], "exploration": [0, 0, 0.02, 0, 0]},
    "locations": ["Exploration"],
    "goodtraits": ["Not Human"]
    },
    {
    "id": "Linen Capelet",
    "desc": "Simple piece of fabric to warn up cold shoulders in unpleasant weather.",
    "icon": "content/items/cape/lcl.png",
    "price": 90,
    "sex": "female",
    "chance": 90,
    "badness": 80,
    "eqchance": 10,
    "hidden": false,
    "infinite": true,
    "slot": "cape",
    "type": "dress",
    "max": {"charisma": 2},
    "mod": {"charisma": 5},
    "locations": ["Tailor Store", "Look around"]
    },
...
it has a number of advantages if you for example wish to allow people to easily add items to your game, almost everyone knows JSON or can find out about it and validate their file. Same thing can be done with Python but you might invite more questions. Also if you have a few hundreds of these, Ren'Py will not scan this files on every startup. It will if you do the same in .rpy files.
Eiliya wrote: As for the attack and defense values, here's how I want it to work. The attacker performs the attack while the defender performs the defend (obviously). These two numbers are generated in exactly the same way, except that the non-random number used for them is different (att for the attacker and def for the defender). You start by generating the result of the random value, which you mentioned previously should be done by a python for loop. There needs to be two random numbers generated, one for the attacker and one for the defender. Onto these values we then add the att and def respectively, before we perform the final check to see if any of these reach the upper limit.

Once we have the final results for these values figured out, we compare them to see if the attack is a hit or not, as well as how much damage it inflicts. I took the code you provided and did some modifications according to how I figure it might work, but if this if flawed, please let me know how and why.

Code: Select all

def result(attacker, defender)
    attack = dice() + attacker.att # if dice is a func
    if attack > x and attacker.lvl < 75:
        attack = 400
    elif attack > y and p1_lvl < 50:
        attack = 320
    defend = dice() + defender.def
    if defend > x and defender.lvl < 75:
        defend = 400
    elif defend > y and defender < 50:
        defend = 320
    result = attack - defend
    return result
It is flawed because you still haven't replaces x and y with anything meaningful. Also there should not be "p1_lvl"... there could be attacker.lvl or defender.lvl. You pass two objects to this function, one object is an instance of the defender, other is the attacker. They carry all the data you've entered into them or changed/added during gameplay. That is what you use... and you access it through fields like:

Code: Select all

attacker.name
defender.hp
attacker.level
defender.image
attacker.portrait
and etc.. you don't have/need all the fields but this is an example.

Eiliya wrote:
xela wrote:As a rule, you generally learn basic Python first, from Code Academy or Tutorials Point or something like that. Then read through Ren'Py Docs and maybe even glance through Python's docs really fast.
I think I mentioned it before, but doing things that way is completely meaningless for me. Even when faced with the most basic of python, I get stuck on things that I do not understand and have to ask someone to provide a more easy to understand explanation. For example, I went to Tutorials Point to check for the Python for Loop that you mentioned, and was faced with a wall of text containing words I didn't understand, code that make no sense to me, code that contains functions I don't know why they are there and strange lists and diagrams.
There are two simple things you need to setup first:

1) Install Python 2.7.8 and learn how to use IDLE. It's an awesome interface where you can test out Python code that you don't understand.

2) Figure out how to use Ren'Py's console and how to check your data structure with it. Like you can type into console: actors, and you'll see what that looks like so you can understand it better. Later you'll figure out how to do stuff like: actors["Noir"].name or actors["Noir"].__dict__ and etc.

And all of it will look strange, until you've messed with it enough so it doesn't. There is no way you can keep asking base questions, even if you get answers, you'll never know any nuances cause noone will retype docs for you and knowing just a few things is not a good option if you're planning to make complex games.
Eiliya wrote:And then we have the code you showed (I took the freedom to modify it a little).

Code: Select all


init python:
    def dice():
        return renpy.random.randint(1, 100)

    def critical_dice():
        result = 0
        d = dice()

        if d < 3:
            result = d - dice()
        else:
             result += d
             if d > 90:
                 for i in [91, 93, 95, 97, 99]:
                     d = dice()
                     if d > i:
                         result += d
                     else:
                         break
        return result
Now this. This makes sense. First we have a function dice which returns the result of the renpy.random.randint function. Then we have the critical_dice which starts by creating the variable result and setting it to 0. It then creates the variable d and connects it to the dice function, which gives d a value between 1 and 100. Then it checks if that value is below 3, which gives the result of d - dice (I'm guessing a new dice is generated here, since the dice() function is mentioned/called). If the result is something else (above 2, in other words), the function then adds the value from d to result, and then checks to see if d is above 90.

Now this is where things get messy for me. I know that the for i in [numbers] means that the code checks for these numbers when making the loop, but I don't understand what the i or the in stands for. Judging from the rest of the code, I'm assuming the i is a new variable that changes it's value (which is taken from the string within the []) each time it loops, starting at the left number and moving towards the right, but I'm not quite sure what part of the code actually makes it loop.

Anyways, I am too tired to properly think on this matter any further tonight, so I will end it with this post. When I return tomorrow, I'll look over any possible replies, and then shuffle around a bit on Tutorial Point trying to make some sense of things over there, but I'm quite sure I'll come back here to ask for further information and clarification in the end. Thanks for all the help thus far, everyone, and I wish you all a good night.

Eiliya wrote:And then we have the code you showed (I took the freedom to modify it a little).

Code: Select all


init python:
    def dice():
        return renpy.random.randint(1, 100)

    def critical_dice():
        result = 0
        d = dice()

        if d < 3:
            result = d - dice()
        else:
             result += d
             if d > 90:
                 for i in [91, 93, 95, 97, 99]:
                     d = dice()
                     if d > i:
                         result += d
                     else:
                         break
        return result
Now this. This makes sense. First we have a function dice which returns the result of the renpy.random.randint function. Then we have the critical_dice which starts by creating the variable result and setting it to 0. It then creates the variable d and connects it to the dice function, which gives d a value between 1 and 100. Then it checks if that value is below 3, which gives the result of d - dice (I'm guessing a new dice is generated here, since the dice() function is mentioned/called). If the result is something else (above 2, in other words), the function then adds the value from d to result, and then checks to see if d is above 90.
So far so good :)
Eiliya wrote: Now this is where things get messy for me. I know that the for i in [numbers] means that the code checks for these numbers when making the loop, but I don't understand what the i or the in stands for. Judging from the rest of the code, I'm assuming the i is a new variable that changes it's value (which is taken from the string within the []) each time it loops, starting at the left number and moving towards the right, but I'm not quite sure what part of the code actually makes it loop.
Well... it's called a for loop, so "for" makes the loop ;)

You got the rest right... i is a variable, you could have written:

Code: Select all

for AbraCaDabRa in [...]:
    ...
but once again, after you get IDLE/console, you do stuff like this:

Code: Select all

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> for AbraCaDabRa in range(10):
	AbraCaDabRa += 78
	print AbraCaDabRa

	
78
79
80
81
82
83
84
85
86
87

Code: Select all

>>> class Actor(object):
	def __init__(self, name, hp):
		self.name = name
		self.hp = hp

		
>>> actors = {}
>>> actor = Actor("Noir", 100)
>>> actors[actor.name] = actor
>>> actor = Actor("Player", 100)
>>> actors[actor.name] = actor
>>> actors
{'Player': <__main__.Actor object at 0x02FBFEB0>, 'Noir': <__main__.Actor object at 0x02EBD850>}
>>> actors["Player"]
<__main__.Actor object at 0x02FBFEB0>
>>> actors["Player"].name
'Player'
>>> actors["Player"].hp
100
>>> player = actors["Player"]
>>> player
<__main__.Actor object at 0x02FBFEB0>
>>> player.hp
100
>>> player.hp += 100
>>> actors["Player"].hp
200
>>> player.hp
200
>>> actors["Noir"]
<__main__.Actor object at 0x02EBD850>
>>> actors["Noir"].__dict__
{'hp': 100, 'name': 'Noir'}
>>> 
You will instantly see what you're doing, get errors which you can fix right on the next input line and test all the gibberish you don't understand until it makes perfect sense to you. This is exactly how I learned, asking questions invited more and more questions until I figured out that I can get answers even to the most complex questions online, test them until I understand what's going on and implement them into the game/code design.
Eiliya wrote: Anyways, I am too tired to properly think on this matter any further tonight, so I will end it with this post. When I return tomorrow, I'll look over any possible replies, and then shuffle around a bit on Tutorial Point trying to make some sense of things over there, but I'm quite sure I'll come back here to ask for further information and clarification in the end. Thanks for all the help thus far, everyone, and I wish you all a good night.
Yeap, be sure to test stuff you find on TP in IDLE. You'll learn 10x as much while testing and exploring than from reading the texts.
Like what we're doing? Support us at:
Image

Eiliya
Regular
Posts: 148
Joined: Tue Dec 04, 2012 6:21 am
Contact:

Re: Requesting feedback regarding script

#13 Post by Eiliya »

Alright, thank you for all the kind feedback. I'll get ahold of this IDLE you spoke of and start by playing around with it.

Post Reply

Who is online

Users browsing this forum: No registered users