diff --git a/docs/1.0-dev/.buildinfo b/docs/1.0-dev/.buildinfo index a808e3f59e..4e7ae5a460 100644 --- a/docs/1.0-dev/.buildinfo +++ b/docs/1.0-dev/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: c0024f80050e9e2b9dfd653b5cfcd067 +config: 7378769b481fe0c2d225b058b431f2ac tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/1.0-dev/Howtos/Coding-FAQ.html b/docs/1.0-dev/Coding/Coding-FAQ.html similarity index 96% rename from docs/1.0-dev/Howtos/Coding-FAQ.html rename to docs/1.0-dev/Coding/Coding-FAQ.html index 76c47b8a07..1d8b43cd27 100644 --- a/docs/1.0-dev/Howtos/Coding-FAQ.html +++ b/docs/1.0-dev/Coding/Coding-FAQ.html @@ -17,8 +17,8 @@ - - + + -

Add this to the default cmdset as usual. The is_full_moon lock +

Add this to the default cmdset as usual. The is_full_moon lock function does not yet exist. We must create that:

# in mygame/server/conf/lockfuncs.py
 
@@ -485,13 +485,13 @@ discussion where some suitable fonts are suggested.

modules |
  • - next |
  • - previous |
  • - +
    develop branch
    diff --git a/docs/1.0-dev/Coding/Coding-Introduction.html b/docs/1.0-dev/Coding/Coding-Introduction.html index 081f47649a..82d4ccb1e2 100644 --- a/docs/1.0-dev/Coding/Coding-Introduction.html +++ b/docs/1.0-dev/Coding/Coding-Introduction.html @@ -17,7 +17,7 @@ - +
    -

    That is, enter evennia. and press the <TAB> key. This will show you all the resources made -available at the top level of Evennia’s “flat API”. See the flat API page for more -info on how to explore it efficiently.

    Jupyter Notebook Support

    You can also explore evennia interactively in a Jupyter notebook. This offers @@ -212,9 +197,7 @@ page. It might hopefully help you avoid some common pitfalls and time sinks.

    home. You should never need to modify anything in the evennia library (anything you download from us, really). You import useful functionality from here and if you see code you like, copy&paste it out into your game folder and edit it there.

    -

    If you find that Evennia doesn’t support some functionality you need, make a Feature -Request about it. Same goes for [bugs][bug]. If you add features or fix bugs -yourself, please consider Contributing your changes upstream!

    +

    If you find that Evennia doesn’t support some functionality you need, make a Feature Request about it. Same goes for bugs. If you add features or fix bugs yourself, please consider Contributing your changes upstream!

    Learn to read tracebacks

    @@ -247,18 +230,6 @@ chat are also there for you.

    The most important point

    And finally, of course, have fun!

    -

    [feature-request]: (https://github.com/evennia/evennia/issues/new?title=Feature+Request:+<descriptive+title+here>&body=####+Description+of+the+suggested+feature+and+how+it+is+supposed+to+work+for+the+admin/end+user: - - -####+A+list+of+arguments+for+why+you+think+this+new+feature+should+be+included+in+Evennia: - -1. -2. - -####+Extra+information,+such+as+requirements+or+ideas+on+implementation: - - -bug

    @@ -278,7 +249,7 @@ chat are also there for you.

    modules |
  • - next |
  • Coding and development help

    This documentation aims to help you set up a sane development environment to -make your game, also if you never coded before. If you are an experienced coder, much of this will be familiar -to you, but some things may still be useful.

    +make your game, also if you never coded before. If you are an experienced coder, much of this will be familiar to you, but some things may still be useful.

    Setting up a workflow

    See also the Beginner Tutorial.

    @@ -142,7 +141,6 @@ to you, but some things may still be useful.

    Previous topic

    -

    Making a sittable object

    +

    Tutorial for basic MUSH like game

    Next topic

    Typeclasses

    @@ -124,6 +124,7 @@
  • Swap typeclass
  • How typeclasses actually work
  • Caveats
  • +
  • Will I run out of dbrefs?
  • Sessions
  • @@ -423,6 +424,13 @@ Due to typeclasses staying so long in memory, stale caches of such relationships visible than common in Django. See the closed issue #1098 and its comments for examples and solutions.

    +
    +

    Will I run out of dbrefs?

    +

    Evennia does not re-use its #dbrefs. This means new objects get an ever-increasing #dbref, also if you delete older objects. There are technical and safety reasons for this. But you may wonder if this means you have to worry about a big game ‘running out’ of dbref integers eventually.

    +

    The answer is simply no.

    +

    For example, the max dbref value for the default sqlite3 database is 2**64. If you created 10 000 new objects every second of every minute of every day of the year it would take about 60 million years for you to run out of dbref numbers. That’s a database of 140 TeraBytes, just to store the dbrefs, no other data.

    +

    If you are still using Evennia at that point and has this concern, get back to us and we can discuss adding dbref reuse then.

    +
    diff --git a/docs/1.0-dev/Concepts/Async-Process.html b/docs/1.0-dev/Concepts/Async-Process.html index d80fa48905..43b3003a32 100644 --- a/docs/1.0-dev/Concepts/Async-Process.html +++ b/docs/1.0-dev/Concepts/Async-Process.html @@ -232,7 +232,7 @@ sleep.

    This will delay the execution of the callback for 10 seconds. This function is explored much more in -the Command Duration Tutorial.

    +the Command Duration Tutorial.

    You can also try the following snippet just see how it works:

    @py from evennia.utils import delay; delay(10, lambda who: who.msg("Test!"), self)
     
    diff --git a/docs/1.0-dev/Concepts/Change-Messages-Per-Receiver.html b/docs/1.0-dev/Concepts/Change-Messages-Per-Receiver.html index afbf2d16c1..51716f4cc7 100644 --- a/docs/1.0-dev/Concepts/Change-Messages-Per-Receiver.html +++ b/docs/1.0-dev/Concepts/Change-Messages-Per-Receiver.html @@ -18,7 +18,7 @@ - + diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Creating-Things.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Creating-Things.html index 2cf3fb026b..2517180188 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Creating-Things.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Creating-Things.html @@ -66,7 +66,14 @@ @@ -109,9 +116,46 @@

    10. Creating things

    -

    We have already created some things - dragons for example. There are many different things to create -in Evennia though. In the last lesson we learned about typeclasses, the way to make objects persistent in the database.

    -

    Given the path to a Typeclass, there are three ways to create an instance of it:

    +

    We have already created some things - dragons for example. There are many different things to create in Evennia though. In the Typeclasses tutorial, we noted that there are 7 default Typeclasses coming with Evennia out of the box:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Evennia base typeclass

    mygame.typeclasses child

    description

    evennia.DefaultObject

    typeclasses.objects.Object

    Everything with a location

    evennia.DefaultCharacter (child of DefaultObject)

    typeclasses.characters.Character

    Player avatars

    evennia.DefaultRoom (child of DefaultObject)

    typeclasses.rooms.Room

    In-game locations

    evennia.DefaultExit (chld of DefaultObject)

    typeclasses.exits.Exit

    Links between rooms

    evennia.DefaultAccount

    typeclasses.accounts.Account

    A player account

    evennia.DefaultChannel

    typeclasses.channels.Channel

    In-game comms

    evennia.DefaultScript

    typeclasses.scripts.Script

    Entities with no location

    +

    Given you have an imported Typeclass, there are four ways to create an instance of it:

    10.1. Creating Objects

    -

    This is one of the most common creation-types. These are entities that inherits from DefaultObject at any distance. -They have an existence in the game world and includes rooms, characters, exits, weapons, flower pots and castles.

    +

    An Object is one of the most common creation-types. These are entities that inherits from DefaultObject at any distance. They have an existence in the game world and includes rooms, characters, exits, weapons, flower pots and castles.

    > py
     > import evennia 
     > rose = evennia.create_object(key="rose")
     
    -

    Since we didn’t specify the typeclass as the first argument, the default given by settings.BASE_OBJECT_TYPECLASS -(typeclasses.objects.Object) will be used.

    +

    Since we didn’t specify the typeclass as the first argument, the default given by settings.BASE_OBJECT_TYPECLASS (typeclasses.objects.Object out of the box) will be used.

    +

    The create_object has a lot of options. A more detailed example in code:

    +
    from evennia import create_object, search_object
    +
    +meadow = search_object("Meadow")[0]
    +
    +lasgun = create_object("typeclasses.objects.guns.LasGun", 
    +					   key="lasgun", 
    +					   location=meadow,
    +					   attributes=[("desc", "A fearsome Lasgun.")])
    +
    +
    +
    +

    Here we set the location of a weapon as well as gave it an Attribute desc, which is what the look command will use when looking this and other things.

    +
    +
    +

    10.2. Creating Rooms, Characters and Exits

    +

    Characters, Rooms and Exits are all subclasses of DefaultObject. So there is for example no separate create_character, you just create characters with create_object pointing to the Character typeclass.

    +
    +

    10.2.1. Linking Exits and Rooms in code

    +

    An Exit is a one-way link between rooms. For example, east could be an Exit between the Forest room and the Meadow room.

    +
    Meadow -> east -> Forest 
    +
    +
    +

    The east exit has a key of east, a location of Meadow and a destination of Forest. If you wanted to be able to go back from Forest to Meadow, you’d need to create a new Exit, say, west, where location is Forest and destination is Meadow.

    +
    Meadow -> east -> Forest 
    +Forest -> west -> Meadow
    +
    +
    +

    In-game you do this with tunnel and dig commands, bit if you want to ever set up these links in code, you can do it like this:

    +
    from evennia import create_object 
    +from mygame.typeclasses import rooms, exits 
    +
    +# rooms
    +meadow = create_object(rooms.Room, key="Meadow")
    +forest = create_object(rooms.Room, key="Forest")
    +
    +# exits 
    +create_object(exits.Exit, key="east", location=meadow, destination=forest)
    +create_object(exits.Exit, key="west", location=forest, destination=meadow)
    +
    +
    +
    -

    10.2. Creating Accounts

    -

    An Account is an out-of-character (OOC) entity, with no existence in the game world. +

    10.3. Creating Accounts

    +

    An Account is an out-of-character (OOC) entity, with no existence in the game world. You can find the parent class for Accounts in typeclasses/accounts.py.

    -

    TODO

    +

    Normally, you want to create the Account when a user authenticates. By default, this happens in the create account and login default commands in the UnloggedInCmdSet. This means that customizing this just means replacing those commands!

    +

    So normally you’d modify those commands rather than make something from scratch. But here’s the principle:

    +
    from evennia import create_account 
    +
    +new_account = create_account(
    +            accountname, email, password, 
    +            permissions=["Player"], 
    +            typeclass="typeclasses.accounts.MyAccount"
    + )
    +
    +
    +

    The inputs are usually taken from the player via the command. The email must be given, but can be None if you are not using it. The accountname must be globally unique on the server. The password is stored encrypted in the database. If typeclass is not given, the settings.BASE_ACCOUNT_TYPECLASS will be used (typeclasses.accounts.Account).

    +
    +
    +

    10.4. Creating Channels

    +

    A Channel acts like a switchboard for sending in-game messages between users; like an IRC- or discord channel but inside the game.

    +

    Users interact with channels via the channel command:

    +
    channel/all 
    +channel/create channelname 
    +channel/who channelname 
    +channel/sub channel name 
    +...
    +(see 'help channel')
    +
    +
    +

    If a channel named, say, myguild exists, a user can send a message to it just by writing the channel name:

    +
    > myguild Hello! I have some questions ... 
    +
    +
    +

    Creating channels follows a familiar syntax:

    +
    from evennia import create_channel
    +
    +new_channel = create_channel(channelname)
    +
    +
    +

    Channels can also be auto-created by the server by setting the DEFAULT_CHANNELS setting. See Channels documentation for details.

    +
    +
    +

    10.5. Creating Scripts

    +

    A Script is an entity that has no in-game location. It can be used to store arbitrary data and is often used for game systems that need persistent storage but which you can’t ‘look’ at in-game. Examples are economic systems, weather and combat handlers.

    +

    Scripts are multi-use and depending on what they do, a given script can either be ‘global’ or be attached “to” another object (like a Room or Character).

    +
    from evennia import create_script, search_object 
    +# global script 
    +new_script = create_script("typeclasses.scripts.MyScript", key="myscript")
    +
    +# on-object script 
    +meadow = search_object("Meadow")[0]
    +new_script = create_script("typeclasses.scripts.MyScripts", 
    +						   key"myscript2", obj=meadow)
    +
    +
    +
    +

    A convenient way to create global scripts is define them in the GLOBAL_SCRIPTS setting; Evennia will then make sure to initialize them. Scripts also have an optional ‘timer’ component. See the dedicated Script documentation for more info.

    +
    +
    +

    10.6. Conclusion

    +

    Any game will need peristent storage of data. This was a quick run-down of how to create each default type of typeclassed entity. If you make your own typeclasses (as children of the default ones), you create them in the same way.

    +

    Next we’ll learn how to find them again by searching for them in the database.

    diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Django-queries.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Django-queries.html index 6aa9b0683a..64d5279683 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Django-queries.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Django-queries.html @@ -17,7 +17,7 @@ - +

    This is an example of a more complex query. We’ll consider it an example of what is possible.

    - -
    from typeclasses.characters import Character
    +
     1
    + 2
    + 3
    + 4
    + 5
    + 6
    + 7
    + 8
    + 9
    +10
    from typeclasses.characters import Character
     
     will_transform = (
    -    Character.objects
    -    .filter(
    -        db_location__db_tags__db_key__iexact="moonlit",
    -        db_attributes__db_key="lycantrophy",
    -        db_attributes__db_value__gt=2
    -    )
    +    Character.objects
    +    .filter(
    +        db_location__db_tags__db_key__iexact="moonlit",
    +        db_attributes__db_key="lycantrophy",
    +        db_attributes__db_value__gt=2
    +    )
     )
    -
    +
    +
      -
    • We want to find Characters, so we access .objects on the Character typeclass.

    • -
    • We start to filter …

    • -
      • -
      • … by accessing the db_location field (usually this is a Room)

      • +
      • Line 4 We want to find Characters, so we access .objects on the Character typeclass.

      • +
      • We start to filter …

        +
          +
        • Line 6: … by accessing the db_location field (usually this is a Room)

          +
          • … and on that location, we get the value of db_tags (this is a many-to-many database field that we can treat like an object for this purpose; it references all Tags on the location)

          • … and from those Tags, we looking for Tags whose db_key is “monlit” (non-case sensitive).

        • -
        • … We also want only Characters with Attributes whose db_key is exactly "lycantrophy"

        • -
        • … at the same time as the Attribute’s db_value is greater-than 2.

        • +
        • Line 7: … We also want only Characters with Attributes whose db_key is exactly "lycantrophy"

        • +
        • Line 8 :… at the same time as the Attribute’s db_value is greater-than 2.

        • +
        +

      Running this query makes our newly lycantrophic Character appear in will_transform so we know to transform it. Success!

      -
      -

      Don’t confuse database fields with Attributes you set via obj.db.attr = 'foo' or -obj.attributes.add(). Attributes are custom database entities linked to an object. They are not -separate fields on that object like db_key or db_location are.

      -
      -
      -

      12.3. Complex queries

      +
      +

      12.3. Queries with OR or NOT

      All examples so far used AND relations. The arguments to .filter are added together with AND (“we want tag room to be “monlit” and lycantrhopy be > 2”).

      -

      For queries using OR and NOT we need Django’s -Q object. It is -imported from Django directly:

      +

      For queries using OR and NOT we need Django’s Q object. It is imported from Django directly:

      from django.db.models import Q
       
      @@ -332,10 +319,11 @@ imported from Django directly:

      You can then use this Q instance as argument in a filter:

      q1 = Q(db_key="foo")
       Character.objects.filter(q1)
      +# this is the same as 
      +Character.objects.filter(db_key="foo")
       
      -

      The useful thing about Q is that these objects can be chained together with special symbols (bit operators): -| for OR and & for AND. A tilde ~ in front negates the expression inside the Q and thus +

      The useful thing about Q is that these objects can be chained together with special symbols (bit operators): | for OR and & for AND. A tilde ~ in front negates the expression inside the Q and thus works like NOT.

      q1 = Q(db_key="Dalton")
       q2 = Q(db_location=prison)
      @@ -344,12 +332,8 @@ Character.objects.filter(q1 | ~q2)
       

      Would get all Characters that are either named “Dalton” or which is not in prison. The result is a mix of Daltons and non-prisoners.

      -

      Let us expand our original werewolf query. Not only do we want to find all -Characters in a moonlit room with a certain level of lycanthrophy. Now we also -want the full moon to immediately transform people who were recently bitten, -even if their lycantrophy level is not yet high enough (more dramatic this -way!). When you get bitten, you’ll get a Tag recently_bitten put on you to -indicate this.

      +

      Let us expand our original werewolf query. Not only do we want to find all Characters in a moonlit room with a certain level of lycanthrophy - we decide that if they have been newly bitten, they should also turn, regardless of their lycantrophy level (more dramatic that way!).

      +

      Let’s say that getting bitten means that you’ll get assigned a Tag recently_bitten.

      This is how we’d change our query:

      from django.db.models import Q
       
      @@ -396,57 +380,67 @@ sure that there is only one instance of each Character in the result.

      12.4. Annotations

      What if we wanted to filter on some condition that isn’t represented easily by a -field on the object? Maybe we want to find rooms only containing five or more -objects?

      +field on the object? An example would wanting to find rooms only containing five or more objects.

      We could do it like this (don’t actually do it this way!):

      -
          from typeclasses.rooms import Room
      +
      from typeclasses.rooms import Room
       
      -      all_rooms = Rooms.objects.all()
      +  all_rooms = Rooms.objects.all()
       
      -      rooms_with_five_objects = []
      -      for room in all_rooms:
      -          if len(room.contents) >= 5:
      -              rooms_with_five_objects.append(room)
      +  rooms_with_five_objects = []
      +  for room in all_rooms:
      +      if len(room.contents) >= 5:
      +          rooms_with_five_objects.append(room)
       
      -

      Above we get all rooms and then use list.append() to keep adding the right +

      +

      Above we get all rooms and then use list.append() to keep adding the right rooms to an ever-growing list. This is not a good idea, once your database -grows this will be unnecessarily computing-intensive. The database is much more -suitable for this.

      +grows this will be unnecessarily compute-intensive. It’s much better to query the +database directly

      Annotations allow you to set a ‘variable’ inside the query that you can then access from other parts of the query. Let’s do the same example as before directly in the database:

      -
      from typeclasses.rooms import Room
      +
      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      from typeclasses.rooms import Room
       from django.db.models import Count
       
       rooms = (
           Room.objects
      -    .annotate(
      -        num_objects=Count('locations_set'))
      -    .filter(num_objects__gte=5)
      -)
      -
      + .annotate( + num_objects=Count('locations_set')) + .filter(num_objects__gte=5) +) +
      +

      Count is a Django class for counting the number of things in the database.

      -

      Here we first create an annotation num_objects of type Count. It creates an in-database function -that will count the number of results inside the database.

      -
      -

      Note the use of location_set in that Count. The *_set is a back-reference automatically created by -Django. In this case it allows you to find all objects that has the current object as location.

      -
      -

      Next we filter on this annotation, using the name num_objects as something we +

        +
      • Line 6-7: Here we first create an annotation num_objects of type Count. It creates an in-database function that will count the number of results inside the database. The fact annotation means that now num_objects is avaiable to be used in other parts of the query.

      • +
      • Line 8 We filter on this annotation, using the name num_objects as something we can filter for. We use num_objects__gte=5 which means that num_objects -should be greater than or equal to 5. This is a little harder to get one’s head -around but much more efficient than lopping over all objects in Python.

        +should be greater than or equal to 5.

      • +
      +

      Annotations can be a little harder to get one’s head around but much more efficient than lopping over all objects in Python.

      12.5. F-objects

      What if we wanted to compare two dynamic parameters against one another in a query? For example, what if instead of having 5 or more objects, we only wanted -objects that had a bigger inventory than they had tags (silly example, but …)? -This can be with Django’s F objects. -So-called F expressions allow you to do a query that looks at a value of each -object in the database.

      +objects that had a bigger inventory than they had tags (silly example, but …)?

      +

      This can be with Django’s F objects. So-called F expressions allow you to do a query that looks at a value of each object in the database.

      from django.db.models import Count, F
       from typeclasses.rooms import Room
       
      @@ -465,34 +459,35 @@ condition to be calculated on the fly, completely within the database.

      12.6. Grouping and returning only certain properties

      -

      Suppose you used tags to mark someone belonging to an organization. Now you want to make a list and -need to get the membership count of every organization all at once.

      -

      The .annotate, .values_list, and .order_by queryset methods are useful for this. Normally when -you run a .filter, what you get back is a bunch of full typeclass instances, like roses or swords. -Using .values_list you can instead choose to only get back certain properties on objects. -The .order_by method finally allows for sorting the results according to some criterion:

      -
      from django.db.models import Count
      +

      Suppose you used tags to mark someone belonging to an organization. Now you want to make a list and need to get the membership count of every organization all at once.

      +

      The .annotate, .values_list, and .order_by queryset methods are useful for this. Normally when you run a .filter, what you get back is a bunch of full typeclass instances, like roses or swords. Using .values_list you can instead choose to only get back certain properties on objects. The .order_by method finally allows for sorting the results according to some criterion:

      +
      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      from django.db.models import Count
       from typeclasses.rooms import Room
       
       result = (
           Character.objects
      -    .filter(db_tags__db_category="organization")
      -    .annotate(tagcount=Count('id'))
      -    .order_by('-tagcount'))
      -    .values_list('db_tags__db_key', "tagcount")
      -
      + .filter(db_tags__db_category="organization") + .annotate(tagcount=Count('id')) + .order_by('-tagcount')) + .values_list('db_tags__db_key', "tagcount") +

      Here we fetch all Characters who …

        -
      • … has a tag of category “organization” on them

      • -
      • … along the way we count how many different Characters (each id is unique) we find for each organization -and store it in a ‘variable’ tagcount using .annotate and Count

      • -
      • … we use this count to sort the result in descending order of tagcount (descending because there is a minus sign, -default is increasing order but we want the most popular organization to be first).

      • -
      • … and finally we make sure to only return exactly the properties we want, namely the name of the organization tag -and how many matches we found for that organization.

      • +
      • Line 6: … has a tag of category “organization” on them

      • +
      • Line 7:… along the way we count how many different Characters (each id is unique) we find for each organization and store it in a ‘variable’ tagcount using .annotate and Count

      • +
      • Line 8: … we use this count to sort the result in descending order of tagcount (descending because there is a minus sign, default is increasing order but we want the most popular organization to be first).

      • +
      • Line 9: … and finally we make sure to only return exactly the properties we want, namely the name of the organization tag and how many matches we found for that organization. For this we use the values_list method on the queryset. This will evaluate the queryset immediately.

      -

      The result queryset will be a list of tuples ordered in descending order by the number of matches, +

      The result will be a list of tuples ordered in descending order by the number of matches, in a format like the following:

      [
        ('Griatch's poets society', 3872),
      @@ -506,11 +501,7 @@ in a format like the following:

      12.7. Conclusions

      -

      We have covered a lot of ground in this lesson and covered several more complex -topics. Knowing how to query using Django is a powerful skill to have.

      -

      This concludes the first part of the Evennia starting tutorial - “What we have”. -Now we have a good foundation to understand how to plan what our tutorial game -will be about.

      +

      We have covered a lot of ground in this lesson and covered several more complex topics. Knowing how to query using Django is a powerful skill to have.

      @@ -530,7 +521,7 @@ will be about.

      modules |
    • - next |
    • 7. Making objects persistent

      Now that we have learned a little about how to find things in the Evennia library, let’s use it.

      In the Python classes and objects lesson we created the dragons Fluffy, Cuddly -and Smaug and made them fly and breathe fire. So far our dragons are short-lived - whenever we restart -the server or quit() out of python mode they are gone.

      +and Smaug and made them fly and breathe fire. So far our dragons are short-lived - whenever we restart the server or quit() out of python mode they are gone.

      This is what you should have in mygame/typeclasses/monsters.py so far:

      
       class Monster:
      diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Making-A-Sittable-Object.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Making-A-Sittable-Object.html
      new file mode 100644
      index 0000000000..38a8313e2a
      --- /dev/null
      +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Making-A-Sittable-Object.html
      @@ -0,0 +1,943 @@
      +
      +
      +
      +
      +  
      +    
      +    
      +
      +    13. Building a chair you can sit on — Evennia 1.0-dev documentation
      +    
      +    
      +    
      +    
      +    
      +    
      +    
      +    
      +    
      +    
      +    
      +     
      +  
      +      
      +
      +    
      + +
      + +
      +
      + +
      +

      13. Building a chair you can sit on

      +

      In this lesson we will make use of what we have learned to create a new game object: a chair you can sit on.

      +

      Out goals are:

      +
        +
      • We want a new ‘sittable’ object, a Chair in particular.

      • +
      • We want to be able to use a command to sit in the chair.

      • +
      • Once we are sitting in the chair it should affect us somehow. To demonstrate this store +the current chair in an attribute is_sitting. Other systems could check this to affect us in different ways.

      • +
      • A character should be able to stand up and move away from the chair.

      • +
      • When you sit down you should not be able to walk to another room without first standing up.

      • +
      +
      +

      13.1. Make us not able to move while resting

      +

      When you are sitting in a chair you can’t just walk off without first standing up. +This requires a change to our Character typeclass. Open mygame/typeclasses/characters.py:

      +
      # in mygame/typeclasses/characters.py
      +
      +# ...
      +
      +class Character(DefaultCharacter):
      +    # ...
      +
      +    def at_pre_move(self, destination):
      +       """
      +       Called by self.move_to when trying to move somewhere. If this returns
      +       False, the move is immediately cancelled.
      +       """
      +       if self.db.is_resting:
      +           self.msg("You need to stand up first.")
      +           return False
      +       return True
      +
      +
      +
      +

      When moving somewhere, character.move_to is called. This in turn +will call character.at_pre_move. If this returns False, the move is aborted.

      +

      Here we look for an Attribute is_resting (which we will assign below) to determine if we are stuck on the chair or not.

      +
      +
      +

      13.2. Making the Chair itself

      +

      Next we need the Chair itself, or rather a whole family of “things you can sit on” that we will call sittables. We can’t just use a default Object since we want a sittable to contain some custom code. We need a new, custom Typeclass. Create a new module mygame/typeclasses/sittables.py with the following content:

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +18
      +19
      +20
      +21
      +22
      +23
      +24
      +25
      # in mygame/typeclasses/sittables.py
      +
      +from typeclasses.objects import Object
      +
      +class Sittable(Object):
      +
      +    def do_sit(self, sitter):
      +        """
      +        Called when trying to sit on/in this object.
      +
      +        Args:
      +            sitter (Object): The one trying to sit down.
      +
      +        """
      +        current = self.db.sitter
      +        if current:
      +            if current == sitter:
      +                sitter.msg(f"You are already sitting on {self.key}.")
      +            else:
      +                sitter.msg(f"You can't sit on {self.key} "
      +                        f"- {current.key} is already sitting there!")
      +            return
      +        self.db.sitting = sitter
      +        sitter.db.is_sitting = self.obj
      +        sitter.msg(f"You sit on {self.key}")
      +
      +
      +

      This handles the logic of someone sitting down on the chair.

      +
        +
      • Line 3: We inherit from the empty Object class in mygame/typeclasses/objects.py. This means we can theoretically modify that in the future and have those changes affect sittables too.

      • +
      • Line 7: The do_sit method expects to be called with the argument sitter, which is to be an Object (most likely a Character). This is the one wanting to sit down.

      • +
      • Line 15: Note that, if the Attribute sitter is not defined on the chair (because this is the first time someone sits in it), this will simply return None, which is fine.

      • +
      • Lines 16-22 We check if someone is already sitting on the chair and returns appropriate error messages depending on if it’s you or someone else. We use return to abort the sit-action.

      • +
      • Line 23: If we get to this point, sitter gets to, well, sit down. We store them in the sitter Attribute on the chair.

      • +
      • Line 24: self.obj is the chair this command is attachd to. We store that in the is_sitting Attribute on the sitter itself.

      • +
      • Line 25: Finally we tell the sitter that they could sit down.

      • +
      +

      Let’s continue:

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      # add this right after the `do_sit method` in the same class 
      +
      +    def do_stand(self, stander):
      +        """
      +        Called when trying to stand from this object.
      +
      +        Args:
      +            stander (Object): The one trying to stand up.
      +
      +        """
      +        current = self.db.sitter
      +        if not stander == current:
      +            stander.msg(f"You are not sitting on {self.key}.")
      +        else:
      +            self.db.sitter = None
      +            del stander.db.is_sitting
      +            stander.msg(f"You stand up from {self.key}")
      +
      +
      +

      This is the inverse of sitting down; we need to do some cleanup.

      +
        +
      • Line 12: If we are not sitting on the chair, it makes no sense to stand up from it.

      • +
      • Line 15: If we get here, we could stand up. We make sure to un-set the sitter Attribute so someone else could use the chair later.

      • +
      • Line 16: The character is no longer sitting, so we delete their is_sitting Attribute. We could also have done stander.db.is_sitting = None here, but deleting the Attribute feels cleaner.

      • +
      • Line 17: Finally, we inform them that they stood up successfully.

      • +
      +

      One could imagine that one could have the future sit command (which we haven’t created yet) check if someone is already sitting in the chair instead. This would work too, but letting the Sittable class handle the logic around who can sit on it makes sense.

      +

      We let the typeclass handle the logic, and also let it do all the return messaging. This makes it easy to churn out a bunch of chairs for people to sit on.

      +
      +

      13.2.1. Sitting on or in?

      +

      It’s fine to sit ‘on’ a chair. But what if our Sittable is an armchair?

      +
      > armchair = evennia.create_object("typeclasses.sittables.Sittable", key="armchair", location=here)
      +> armchair.do_sit(me)
      +> You sit on armchair.
      +
      +
      +

      This is not grammatically correct, you actually sit “in” an armchair rather than “on” it. It’s also possible to both sit ‘in’ or ‘on’ a chair depending on the type of chair (English is weird). We want to be able to control this.

      +

      We could make a child class of Sittable named SittableIn that makes this change, but that feels excessive. Instead we will modify what we have:

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +18
      +19
      +20
      +21
      +22
      +23
      +24
      +25
      +26
      +27
      +28
      +29
      +30
      +31
      +32
      +33
      +34
      +35
      +36
      +37
      +38
      +39
      +40
      +41
      +42
      +43
      # in mygame/typeclasses/sittables.py
      +
      +from evennia import DefaultObject
      +
      +class Sittable(DefaultObject):
      +
      +    def do_sit(self, sitter):
      +        """
      +        Called when trying to sit on/in this object.
      +
      +        Args:
      +            sitter (Object): The one trying to sit down.
      +
      +        """
      +        adjective = self.db.adjective or "on"
      +        current = self.db.sitter
      +        if current:
      +            if current == sitter:
      +                sitter.msg(f"You are already sitting {adjective} {self.key}.")
      +            else:
      +                sitter.msg(
      +                    f"You can't sit {adjective} {self.key} "
      +                    f"- {current.key} is already sitting there!")
      +            return
      +        self.db.sitting = sitter
      +        sitter.db.is_sitting = self.obj
      +        sitter.msg(f"You sit {adjective} {self.key}")
      +
      +    def do_stand(self, stander):
      +        """
      +        Called when trying to stand from this object.
      +
      +        Args:
      +            stander (Object): The one trying to stand up.
      +
      +        """
      +        current = self.db.sitter
      +        if not stander == current:
      +            stander.msg(f"You are not sitting {self.db.adjective} {self.key}.")
      +        else:
      +            self.db.sitting = None
      +            del stander.db.is_sitting
      +            stander.msg(f"You stand up from {self.key}")
      +
      +
      +
        +
      • Line 15: We grab the adjective Attribute. Using seld.db.adjective or "on" here means that if the Attribute is not set (is None/falsy) the default “on” string will be assumed.

      • +
      • Lines 22 and 43: We use this adjective to modify the return text we see.

      • +
      +

      reload the server. An advantage of using Attributes like this is that they can be modified on the fly, in-game. Let’s look at a builder could use this by normal building commands (no need for py):

      +
      > set armchair/adjective = in 
      +
      +
      +

      Since we haven’t added the sit command yet, we must still use py to test:

      +
      > py armchair = evennia.search_object("armchair")[0];armchair.do_sit(me)
      +You sit in armchair.
      +
      +
      +
      +
      +

      13.2.2. Extra credits

      +

      What if we want some more dramatic flair when you sit down in certain chairs?

      +
      You sit down and a whoopie cushion makes a loud fart noise!
      +
      +
      +

      You can make this happen by tweaking your Sittable class having the return messages be replaceable by Attributes that you can set on the object you create. You want something like this:

      +
      > chair = evennia.create_object("typeclasses.sittables.Sittable", key="pallet")
      +> chair.do_sit(me)
      +You sit down on pallet.
      +> chair.do_stand(me)
      +You stand up from pallet.
      +> chair.db.msg_sitting_down = "You sit down and a whoopie cushion makes a loud fart noise!"
      +> chair.do_sit(me)
      +You sit down and a whoopie cushion makes a loud fart noise!
      +
      +
      +

      That is, if you are not setting the Attribute, you should get a default value. We leave this implementation up to the reader.

      +
      +
      +
      +

      13.3. Adding commands

      +

      As we discussed in the lesson about adding Commands, there are two main ways to design the commands for sitting and standing up:

      +
        +
      • You can store the commands on the chair so they are only available when a chair is in the room

      • +
      • You can store the commands on the Character so they are always available and you must always specify which chair to sit on.

      • +
      +

      Both of these are very useful to know about, so in this lesson we’ll try both.

      +
      +

      13.3.1. Command variant 1: Commands on the chair

      +

      This way to implement sit and stand puts new cmdsets on the Sittable itself. +As we’ve learned before, commands on objects are made available to others in the room. +This makes the command easy but instead adds some complexity in the management of the CmdSet.

      +

      This is how it could look if armchair is in the room (if you overrode the sit message):

      +
      > sit
      +As you sit down in armchair, life feels easier.
      +
      +
      +

      What happens if there are sittables sofa and barstool also in the room? Evennia will automatically +handle this for us and allow us to specify which one we want:

      +
      > sit
      +More than one match for 'sit' (please narrow target):
      + sit-1 (armchair)
      + sit-2 (sofa)
      + sit-3 (barstool)
      +> sit-1
      +As you sit down in armchair, life feels easier.
      +
      +
      +

      To keep things separate we’ll make a new module mygame/commands/sittables.py:

      + +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +18
      +19
      +20
      +21
      +22
      +23
      +24
      +25
      +26
      # in mygame/commands/sittables.py 
      +
      +from evennia import Command, CmdSet
      +
      +class CmdSit(Command):
      +    """
      +    Sit down.
      +    """
      +    key = "sit"
      +    def func(self):
      +        self.obj.do_sit(self.caller)
      +
      +class CmdStand(Command):
      +     """
      +     Stand up.
      +     """
      +     key = "stand"
      +     def func(self):
      +         self.obj.do_stand(self.caller)
      +
      +
      +class CmdSetSit(CmdSet):
      +    priority = 1
      +    def at_cmdset_creation(self):
      +        self.add(CmdSit)
      +        self.add(CmdStand)
      +
      +
      +

      As seen, the commands are nearly trivial.

      +
        +
      • Lines 11 and 19: The self.obj is the object to which we added the cmdset with this Command (so the chair). We just call the do_sit/stand on that object and pass the caller (the person sitting down). The Sittable will do the rest.

      • +
      • Line 23: The priority = 1 on CmdSetSit means that same-named Commands from this cmdset merge with a bit higher priority than Commands from the on-Character-cmdset (which has priority = 0). This means that if you have a sit command on your Character and comes into a room with a chair, the sit command on the chair will take precedence.

      • +
      +

      We also need to make a change to our Sittable typeclass. Open mygame/typeclasses/sittables.py:

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      # in mygame/typeclasses/sittables.py
      +
      +from evennia import DefaultObject
      +from commands.sittables import CmdSetSit 
      +
      +class Sittable(DefaultObject):
      +    """
      +    (docstring)
      +    """
      +    def at_object_creation(self):
      +        self.cmdset.add_default(CmdSetSit)A
      +    # ... 
      +
      +
      +
        +
      • Line 4: We must install the CmdSetSit .

      • +
      • Line 10: The at_object_creation method will only be called once, when the object is first created.

      • +
      • Line 11: We add the command-set as a ‘default’ cmdset with add_default. This makes it persistent also protects it from being deleted should another cmdset be added. See Command Sets for more info.

      • +
      +

      Make sure to reload to make the code changes available.

      +

      All new Sittables will now have your sit Command. Your existing armchair will not though. This is because at_object_creation will not re-run for already existing objects. We can update it manually:

      +
      > update armchair
      +
      +
      +

      We could also update all existing sittables (all on one line):

      + +
      > py from typeclasses.sittables import Sittable ;
      +       [sittable.at_object_creation() for sittable in Sittable.objects.all()]
      +
      +
      +

      We should now be able to use sit while in the room with the armchair.

      +
      > sit
      +As you sit down in armchair, life feels easier.
      +> stand
      +You stand up from armchair.
      +
      +
      +

      One issue with placing the sit (or stand) Command “on” the chair is that it will not be available when in a room without a Sittable object:

      +
      > sit
      +Command 'sit' is not available. ...
      +
      +
      +

      This is practical but not so good-looking; it makes it harder for the user to know a sit action is at all possible. Here is a trick for fixing this. Let’s add another Command to the bottom +of mygame/commands/sittables.py:

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      +15
      # after the other commands in mygame/commands/sittables.py
      +# ...
      +
      +class CmdNoSitStand(Command):
      +    """
      +    Sit down or Stand up
      +    """
      +    key = "sit"
      +    aliases = ["stand"]
      +
      +    def func(self):
      +        if self.cmdname == "sit":
      +            self.msg("You have nothing to sit on.")
      +        else:
      +            self.msg("You are not sitting down.")
      +
      +
      +
        +
      • Line 9: This command responds both to sit and stand because we added stand to its aliases list. Command aliases have the same ‘weight’ as the key of the command, both equally identify the Command.

      • +
      • Line 12: The .cmdname of a Command holds the name actually used to call it. This will be one of "sit" or "stand". This leads to different return messages.

      • +
      +

      We don’t need a new CmdSet for this, instead we will add this to the default Character cmdset. Open mygame/commands/default_cmdsets.py:

      +
      # in mygame/commands/default_cmdsets.py
      +
      +# ...
      +from commands import sittables
      +
      +class CharacterCmdSet(CmdSet):
      +    """
      +    (docstring)
      +    """
      +    def at_cmdset_creation(self):
      +        # ...
      +        self.add(sittables.CmdNoSitStand)
      +
      +
      +
      +

      As usual, make sure to reload the server to have the new code recognized.

      +

      To test we’ll build a new location without any comfy armchairs and go there:

      +
      > tunnel n = kitchen
      +north
      +> sit
      +You have nothing to sit on.
      +> south
      +sit
      +As you sit down in armchair, life feels easier.
      +
      +
      +

      We now have a fully functioning sit action that is contained with the chair itself. When no chair is around, a default error message is shown.

      +

      How does this work? There are two cmdsets at play, both of which have a sit/stand Command - one on the Sittable (armchair) and the other on us (via the CharacterCmdSet). Since we set a priority=1 on the chair’s cmdset (and CharacterCmdSet has priority=0), there will be no command-collision: the chair’s sit takes precedence over the sit defined on us … until there is no chair around.

      +

      So this handles sit. What about stand? That will work just fine:

      +
      > stand
      +You stand up from armchair.
      +> north
      +> stand
      +You are not sitting down.
      +
      +
      +

      We have one remaining problem with stand though - what happens when you are sitting down and try to stand in a room with more than one Sittable:

      +
      > stand
      +More than one match for 'stand' (please narrow target):
      + stand-1 (armchair)
      + stand-2 (sofa)
      + stand-3 (barstool)
      +
      +
      +

      Since all the sittables have the stand Command on them, you’ll get a multi-match error. This works … but you could pick any of those sittables to “stand up from”. That’s really weird.

      +

      With sit it was okay to get a choice - Evennia can’t know which chair we intended to sit on. But once we sit we sure know from which chair we should stand up from! We must make sure that we only get the command from the chair we are actually sitting on.

      +

      We will fix this with a Lock and a custom lock function. We want a lock on the stand Command that only makes it available when the caller is actually sitting on the chair that particular stand command is attached to.

      +

      First let’s add the lock so we see what we want. Open mygame/commands/sittables.py:

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      # in mygame/commands/sittables.py
      +
      +# ...
      +
      +class CmdStand(Command):
      +     """
      +     Stand up.
      +     """
      +     key = "stand"
      +     lock = "cmd:sitsonthis()"
      +
      +     def func(self):
      +         self.obj.do_stand(self.caller)
      +# ...
      +
      +
      +
        +
      • Line 10: This is the lock definition. It’s on the form condition:lockfunc. The cmd: type lock is checked by Evennia when determining if a user has access to a Command at all. We want the lock-function to only return True if this command is on a chair which the caller is sitting on. +What will be checked is the sitsonthis lock function which doesn’t exist yet.

      • +
      +

      Open mygame/server/conf/lockfuncs.py to add it!

      +
      # mygame/server/conf/lockfuncs.py
      +
      +"""
      +(module lockstring)
      +"""
      +# ...
      +
      +def sitsonthis(accessing_obj, accessed_obj, *args, **kwargs):
      +    """
      +    True if accessing_obj is sitting on/in the accessed_obj.
      +    """
      +    return accessed_obj.db.sitting == accessing_obj
      +
      +# ...
      +
      +
      +

      Evennia knows that all functions in mygame/server/conf/lockfuncs should be possible to use in a lock definition.

      +

      All lock functions must acccept the same arguments. The arguments are required and Evennia will pass all relevant objects as needed.

      + +
        +
      • accessing_obj is the one trying to access the lock. So us, in this case.

      • +
      • accessed_obj is the entity we are trying to gain a particular type of access to. So the chair.

      • +
      • args is a tuple holding any arguments passed to the lockfunc. Since we use sitsondthis() this will be empty (and if we add anything, it will be ignored).

      • +
      • kwargs is a tuple of keyword arguments passed to the lockfuncs. This will be empty as well in our example.

      • +
      +

      Make sure you reload.

      +

      If you are superuser, it’s important that you quell yourself before trying this out. This is because the superuser bypasses all locks - it can never get locked out, but it means it will also not see the effects of a lock like this.

      +
      > quell
      +> stand
      +You stand up from armchair
      +
      +
      +

      None of the other sittables’ stand commands passed the lock and only the one we are actually sitting on did! This is a fully functional chair now!

      +

      Adding a Command to the chair object like this is powerful and is a good technique to know. It does come with some caveats though, as we’ve seen.

      +

      We’ll now try another way to add the sit/stand commands.

      +
      +
      +

      13.3.2. Command variant 2: Command on Character

      +

      Before we start with this, delete the chairs you’ve created:

      +
      > del armchair 
      +> del sofa 
      +> (etc)
      +
      +
      +

      The make the following changes:

      +
        +
      • In mygame/typeclasses/sittables.py, comment out the entire at_object_creation method.

      • +
      • In mygame/commands/default_cmdsets.py, comment out the line self.add(sittables.CmdNoSitStand).

      • +
      +

      This disables the on-object command solution so we can try an alternative. Make sure to reload so the changes are known to Evennia.

      +

      In this variation we will put the sit and stand commands on the Character instead of on the chair. This makes some things easier, but makes the Commands themselves more complex because they will not know which chair to sit on. We can’t just do sit anymore. This is how it will work.

      +
      > sit <chair>
      +You sit on chair.
      +> stand
      +You stand up from chair.
      +
      +
      +

      Open mygame/commands/sittables.py again. We’ll add a new sit-command. We name the class CmdSit2 since we already have CmdSit from the previous example. We put everything at the end of the module to keep it separate.

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +18
      +19
      +20
      +21
      +22
      +23
      +24
      +25
      +26
      +27
      +28
      +29
      +30
      +31
      +32
      +33
      +34
      +35
      +36
      +37
      +38
      # in mygame/commands/sittables.py
      +
      +from evennia import Command, CmdSet
      +from evennia import InterruptCommand
      +
      +class CmdSit(Command):
      +    # ...
      +
      +# ...
      +
      +# new from here
      +
      +class CmdSit2(Command):
      +    """
      +    Sit down.
      +
      +    Usage:
      +        sit <sittable>
      +
      +    """
      +    key = "sit"
      +
      +    def parse(self):
      +        self.args = self.args.strip()
      +        if not self.args:
      +            self.caller.msg("Sit on what?")
      +            raise InterruptCommand
      +
      +    def func(self):
      +
      +        # self.search handles all error messages etc.
      +        sittable = self.caller.search(self.args)
      +        if not sittable:
      +            return
      +        try:
      +            sittable.do_sit(self.caller)
      +        except AttributeError:
      +            self.caller.msg("You can't sit on that!")
      +
      +
      + +
        +
      • Line 4: We need the InterruptCommand to be able to abort command parsing early (see below).

      • +
      • Line 27: The parse method runs before the func method on a Command. If no argument is provided to the command, we want to fail early, already in parse, so func never fires. Just return is not enough to do that, we need to raise InterruptCommand. Evennia will see a raised InterruptCommand as a sign it should immediately abort the command execution.

      • +
      • Line 32: We use the parsed command arguments as the target-chair to search for. As discussed in the search tutorial, self.caller.search() will handle error messages itself. So if it returns None, we can just return.

      • +
      • Line 35-38: The try...except block ‘catches’ and exception and handles it. In this case we try to run do_sit on the object. If the object we found is not a Sittable, it will likely not have a do_sit method and an AttributeError will be raised. We should handle that case gracefully.

      • +
      +

      Let’s do the stand command while we are at it. Since the Command is external to the chair we don’t know which object we are sitting on and have to search for it. In this case we really want to find only things we are sitting on.

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +18
      +19
      +20
      +21
      # end of mygame/commands/sittables.py
      +
      +class CmdStand2(Command):
      +    """
      +    Stand up.
      +
      +    Usage:
      +        stand
      +
      +    """
      +    key = "stand"
      +
      +    def func(self):
      +
      +    caller = self.caller
      +    # if we are sitting, this should be set on us
      +    sittable = caller.db.is_sitting
      +    if not sittable:
      +        caller.msg("You are not sitting down.")
      +    else:
      +        sittable.do_stand(caller)
      +
      +
      +
        +
      • Line 17: We didn’t need the is_sitting Attribute for the first version of these Commands, but we do need it now. Since we have this, we don’t need to search and know just which chair we sit on. If we don’t have this set, we are not sitting anywhere.

      • +
      • Line 21: We stand up using the sittable we found.

      • +
      +

      All that is left now is to make this available to us. This type of Command should be available to us all the time so we can put it in the default Cmdset on the Character. Open mygame/commands/default_cmdsets.py.

      +
      # in mygame/commands/default_cmdsets.py
      +
      +# ...
      +from commands import sittables
      +
      +class CharacterCmdSet(CmdSet):
      +    """
      +    (docstring)
      +    """
      +    def at_cmdset_creation(self):
      +        # ...
      +        self.add(sittables.CmdSit2)
      +        self.add(sittables.CmdStand2)
      +
      +
      +
      +

      Make sure to reload.

      +

      Now let’s try it out:

      +
      > create/drop sofa : sittables.Sittable
      +> sit sofa
      +You sit down on sofa.
      +> stand
      +You stand up from sofa.
      +> north 
      +> sit sofa 
      +> You can't find 'sofa'.
      +
      +
      +

      Storing commands on the Character centralizes them, but you must instead search or store any external objects you want that command to interact on.

      +
      +
      +
      +

      13.4. Conclusions

      +

      In this lesson we built ourselves a chair and even a sofa!

      +
        +
      • We modified our Character class to avoid moving when sitting down.

      • +
      • We made a new Sittable typeclass

      • +
      • We tried two ways to allow a user to interact with sittables using sit and stand commands.

      • +
      +

      Eagle-eyed readers will notice that the stand command sitting “on” the chair (variant 1) would work just fine together with the sit command sitting “on” the Character (variant 2). There is nothing stopping you from mixing them, or even try a third solution that better fits what you have in mind.

      +

      This concludes the first part of the Beginner tutorial!

      +
      +
      + + +
      +
      +
      + +
      + + + + \ No newline at end of file diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-More-on-Commands.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-More-on-Commands.html index 2a1dfc12ac..741f9cf751 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-More-on-Commands.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-More-on-Commands.html @@ -122,7 +122,7 @@ also learn how to add, modify and extend Evennia’s default commands.

      9.1. More advanced parsing

      -

      In the last lesson we made a hit Command and hit a dragon with it. You should have the code +

      In the last lesson we made a hit Command and struck a dragon with it. You should have the code from that still around.

      Let’s expand our simple hit command to accept a little more complex input:

      hit <target> [[with] <weapon>]
      @@ -135,9 +135,50 @@ hit target with weapon
       

      If you don’t specify a weapon you’ll use your fists. It’s also nice to be able to skip “with” if -you are in a hurry. Time to modify mygame/commands/mycommands.py again. Let us break out the parsing -a little, in a new method parse:

      -
      #...
      +you are in a hurry. Time to modify mygame/commands/mycommands.py again. Let us break out the parsing a little, in a new method parse:

      +
       1
      + 2
      + 3
      + 4
      + 5
      + 6
      + 7
      + 8
      + 9
      +10
      +11
      +12
      +13
      +14
      +15
      +16
      +17
      +18
      +19
      +20
      +21
      +22
      +23
      +24
      +25
      +26
      +27
      +28
      +29
      +30
      +31
      +32
      +33
      +34
      +35
      +36
      +37
      +38
      +39
      +40
      +41
      +42
      +43
      #...
       
       class CmdHit(Command):
           """
      @@ -150,12 +191,12 @@ a little, in a new method key = "hit"
       
           def parse(self):
      -        self.args = self.args.strip()
      -        target, *weapon = self.args.split(" with ", 1)
      -        if not weapon:
      -            target, *weapon = target.split(" ", 1)
      -        self.target = target.strip()
      -        if weapon:
      +        self.args = self.args.strip()
      +        target, *weapon = self.args.split(" with ", 1)
      +        if not weapon:
      +            target, *weapon = target.split(" ", 1)
      +        self.target = target.strip()
      +        if weapon:
                   self.weapon = weapon.strip()
               else:
                   self.weapon = ""
      @@ -165,28 +206,24 @@ a little, in a new method self.caller.msg("Who do you want to hit?")
                   return
               # get the target for the hit
      -        target = self.caller.search(self.target)
      -        if not target:
      +        target = self.caller.search(self.target)
      +        if not target:
                   return
               # get and handle the weapon
               weapon = None
               if self.weapon:
      -            weapon = self.caller.search(self.weapon)
      -        if weapon:
      +            weapon = self.caller.search(self.weapon)
      +        if weapon:
                   weaponstr = f"{weapon.key}"
               else:
                   weaponstr = "bare fists"
       
      -        self.caller.msg(f"You hit {target.key} with {weaponstr}!")
      -        target.msg(f"You got hit by {self.caller.key} with {weaponstr}!")
      +        self.caller.msg(f"You hit {target.key} with {weaponstr}!")
      +        target.msg(f"You got hit by {self.caller.key} with {weaponstr}!")
       # ...
      -
      -
      +
      -

      The parse method is called before func and has access to all the same on-command variables as in func. Using -parse not only makes things a little easier to read, it also means you can easily let other Commands inherit -your parsing - if you wanted some other Command to also understand input on the form <arg> with <arg> you’d inherit -from this class and just implement the func needed for that command without implementing parse anew.

      +

      The parse method is a special one Evennia knows to call before func. At this time it has access to all the same on-command variables as func does. Using parse not only makes things a little easier to read, it also means you can easily let other Commands inherit your parsing - if you wanted some other Command to also understand input on the form <arg> with <arg> you’d inherit from this class and just implement the func needed for that command without implementing parse anew.

      +

      Poor Smaug.

      9.2. Adding a Command to an object

      -

      The commands of a cmdset attached to an object with obj.cmdset.add() will by default be made available to that object -but also to those in the same location as that object. If you did the Building introduction -you’ve seen an example of this with the “Red Button” object. The Tutorial world -also has many examples of objects with commands on them.

      + +

      As we learned in the lesson about Adding commands, Commands are are grouped in Command Sets. Such Command Sets are attached to an object with obj.cmdset.add() and will then be available for that object to use.

      +

      What we didn’t mention before is that by default those commands are also available to those in the same location as that object. If you did the Building quickstart lesson you’ve seen an example of this with the “Red Button” object. The Tutorial world also has many examples of objects with commands on them.

      To show how this could work, let’s put our ‘hit’ Command on our simple sword object from the previous section.

      > self.search("sword").cmdset.add("commands.mycommands.MyCmdSet", persistent=True)
       
      @@ -284,12 +326,9 @@ hit-2
      -

      Woah, that didn’t go as planned. Evennia actually found two hit commands to didn’t know which one to use -(we know they are the same, but Evennia can’t be sure of that). As we can see, hit-1 is the one found on -the sword. The other one is from adding MyCmdSet to ourself earlier. It’s easy enough to tell Evennia which -one you meant:

      +

      Woah, that didn’t go as planned. Evennia actually found two hit commands and didn’t know which one to use (we know they are the same, but Evennia can’t be sure of that). As we can see, hit-1 is the one found on the sword. The other one is from adding MyCmdSet to ourself earlier. It’s easy enough to tell Evennia which one you meant:

      > hit-1
       Who do you want to hit?
       > hit-2
      @@ -302,7 +341,7 @@ Who do you want to hit?
       Who do you want to hit?
       
      -

      Now try this:

      +

      Now try making a new location and then drop the sword in it.

      > tunnel n = kitchen
       > n
       > drop sword
      @@ -314,24 +353,19 @@ Command 'hit' is not available. Maybe you meant ...
       Who do you want to hit?
       
      -

      The hit command is now only available if you hold or are in the same room as the sword.

      +

      The hit command is only available if you hold or are in the same room as the sword.

      9.2.1. You need to hold the sword!

      -

      Let’s get a little ahead of ourselves and make it so you have to hold the sword for the hit command to -be available. This involves a Lock. We’ve cover locks in more detail later, just know that they are useful -for limiting the kind of things you can do with an object, including limiting just when you can call commands on -it.

      +

      Let’s get a little ahead of ourselves and make it so you have to hold the sword for the hit command to be available. This involves a Lock. We’ve cover locks in more detail later, just know that they are useful for limiting the kind of things you can do with an object, including limiting just when you can call commands on it.

      > py self.search("sword").locks.add("call:holds()")
       
      -

      We added a new lock to the sword. The lockstring "call:holds()" means that you can only call commands on -this object if you are holding the object (that is, it’s in your inventory).

      -

      For locks to work, you cannot be superuser, since the superuser passes all locks. You need to quell yourself -first:

      +

      We added a new lock to the sword. The lockstring "call:holds()" means that you can only call commands on this object if you are holding the object (that is, it’s in your inventory).

      +

      For locks to work, you cannot be superuser, since the superuser passes all locks. You need to quell yourself first:

      -

      Finally, we get rid of ours sword so we have a clean slate with no more hit commands floating around. -We can do that in two ways:

      +

      Finally, we get rid of ours sword so we have a clean slate with no more hit commands floating around. We can do that in two ways:

      delete sword
       
      @@ -360,17 +393,7 @@ We can do that in two ways:

      9.3. Adding the Command to a default Cmdset

      -

      As we have seen we can use obj.cmdset.add() to add a new cmdset to objects, whether that object -is ourself (self) or other objects like the sword.

      -

      This is how all commands in Evennia work, including default commands like look, dig, inventory and so on. -All these commands are in just loaded on the default objects that Evennia provides out of the box.

      -
        -
      • Characters (that is ‘you’ in the gameworld) has the CharacterCmdSet.

      • -
      • Accounts (the thing that represents your out-of-character existence on the server) has the AccountCmdSet

      • -
      • Sessions (representing one single client connection) has the SessionCmdSet

      • -
      • Before you log in (at the connection screen) you’ll have access to the UnloggedinCmdSet.

      • -
      -

      The thing must commonly modified is the CharacterCmdSet.

      +

      As we have seen we can use obj.cmdset.add() to add a new cmdset to objects, whether that object is ourself (self) or other objects like the sword. Doing this this way is a little cumbersome though. It would be better to add this to all characters.

      The default cmdset are defined in mygame/commands/default_cmdsets.py. Open that file now:

      """
       (module docstring)
      @@ -427,14 +450,15 @@ All these commands are in just loaded on the default objects that Evennia provid
       
       

      The super() function refers to the parent of the current class and is commonly used to call same-named methods on the parent.

      -

      evennia.default_cmds is a container that holds all of Evennia’s default commands and cmdsets. In this module -we can see that this was imported and then a new child class was made for each cmdset. Each class looks familiar -(except the key, that’s mainly used to easily identify the cmdset in listings). In each at_cmdset_creation all -we do is call super().at_cmdset_creation which means that we call `at_cmdset_creation() on the parent CmdSet. +

      evennia.default_cmds is a container that holds all of Evennia’s default commands and cmdsets. In this module we can see that this was imported and then a new child class was made for each cmdset. Each class looks familiar (except the key, that’s mainly used to easily identify the cmdset in listings). In each at_cmdset_creation all we do is call super().at_cmdset_creation which means that we call `at_cmdset_creation() on the parent CmdSet. This is what adds all the default commands to each CmdSet.

      -

      To add even more Commands to a default cmdset, we can just add them below the super() line. Usefully, if we were to -add a Command with the same .key as a default command, it would completely replace that original. So if you were -to add a command with a key look, the original look command would be replaced by your own version.

      +

      When the DefaultCharacter (or a child of it) is created, you’ll find that the equivalence of self.cmdset.add("default_cmdsets.CharacterCmdSet, persistent=True") gets called. This means that all new Characters get this cmdset. After adding more commands to it, you just need to reload to have all characters see it.

      +
        +
      • Characters (that is ‘you’ in the gameworld) has the CharacterCmdSet.

      • +
      • Accounts (the thing that represents your out-of-character existence on the server) has the AccountCmdSet

      • +
      • Sessions (representing one single client connection) has the SessionCmdSet

      • +
      • Before you log in (at the connection screen) your Session have access to the UnloggedinCmdSet.

      • +

      For now, let’s add our own hit and echo commands to the CharacterCmdSet:

      # ...
       
      @@ -460,8 +484,7 @@ to add a command with a key 
      -

      Your new commands are now available for all player characters in the game. There is another way to add a bunch -of commands at once, and that is to add a CmdSet to the other cmdset. All commands in that cmdset will then be added:

      +

      Your new commands are now available for all player characters in the game. There is another way to add a bunch of commands at once, and that is to add your own CmdSet to the other cmdset.

      from commands import mycommands
       
       class CharacterCmdSet(default_cmds.CharacterCmdSet):
      @@ -485,7 +508,7 @@ this is practical. A Command can be a part of any number of different CmdSets.mygame/commands/default_cmdsets.py. But what if you want to remove a default command?

      We already know that we use cmdset.remove() to remove a cmdset. It turns out you can do the same in at_cmdset_creation. For example, let’s remove the default get Command -from Evennia. We happen to know this can be found as default_cmds.CmdGet.

      +from Evennia. If you investigate the default_cmds.CharacterCmdSet parent, you’ll find that its class is default_cmds.CmdGet (the ‘real’ location is evennia.commands.default.general.CmdGet).

      # ...
       from commands import mycommands
       
      @@ -515,23 +538,28 @@ Command 'get' is not available ...
       

      9.4. Replace a default command

      At this point you already have all the pieces for how to do this! We just need to add a new command with the same key in the CharacterCmdSet to replace the default one.

      -

      Let’s combine this with what we know about classes and -how to override a parent class. Open mygame/commands/mycommands.py and lets override -that CmdGet command.

      -
      # up top, by the other imports
      -from evennia import default_cmds
      -
      +

      Let’s combine this with what we know about classes and how to override a parent class. Open mygame/commands/mycommands.py and make a new get command:

      +
      1
      +2
      +3
      +4
      +5
      +6
      +7
      +8
      +9
      # up top, by the other imports
      +from evennia import default_cmds
      +
       # somewhere below
       class MyCmdGet(default_cmds.CmdGet):
       
      -    def func(self):
      -        super().func()
      -        self.caller.msg(str(self.caller.location.contents))
      -
      -
      + def func(self): + super().func() + self.caller.msg(str(self.caller.location.contents)) +
        -
      • Line2: We import default_cmds so we can get the parent class. +

      • Line 2: We import default_cmds so we can get the parent class. We made a new class and we make it inherit default_cmds.CmdGet. We don’t need to set .key or .parse, that’s already handled by the parent. In func we call super().func() to let the parent do its normal thing,

      • @@ -565,6 +593,7 @@ has a special function # ...
      +

      We don’t need to use self.remove() first; just adding a command with the same key (get) will replace the default get we had from before.

      -

      We just made a new get-command that tells us everything we could pick up (well, we can’t pick up ourselves, so -there’s some room for improvement there).

      +

      We just made a new get-command that tells us everything we could pick up (well, we can’t pick up ourselves, so there’s some room for improvement there …).

      9.5. Summary

      -

      In this lesson we got into some more advanced string formatting - many of those tricks will help you a lot in -the future! We also made a functional sword. Finally we got into how to add to, extend and replace a default -command on ourselves.

      +

      In this lesson we got into some more advanced string formatting - many of those tricks will help you a lot in the future! We also made a functional sword. Finally we got into how to add to, extend and replace a default command on ourselves. Knowing to add commands is a big part of making a game!

      +

      We have been beating on poor Smaug for too long. Next we’ll create more things to play around with.

      diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Part1-Intro.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Part1-Intro.html index 725fb57b37..db1c5afa4c 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Part1-Intro.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Part1-Intro.html @@ -65,7 +65,6 @@ @@ -134,25 +133,6 @@ these concepts in the context of Evennia before.

      Lessons

      - -
      -

      Table of Contents

      -
      diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Searching-Things.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Searching-Things.html index 107f192465..54d292a86e 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Searching-Things.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Searching-Things.html @@ -126,30 +126,31 @@ if we cannot find and use it afterwards.

      11.1. Main search functions

      The base tools are the evennia.search_* functions, such as evennia.search_object.

      -
          rose = evennia.search_object(key="rose")
      -    acct = evennia.search_account(key="MyAccountName", email="foo@bar.com")
      +
      import evennia 
      +
      +roses = evennia.search_object(key="rose")
      +accts = evennia.search_account(key="MyAccountName", email="foo@bar.com")
       
      - -

      Strings are always case-insensitive, so searching for "rose", "Rose" or "rOsE" give the same results. -It’s important to remember that what is returned from these search methods is a listing of 0, one or more -elements - all the matches to your search. To get the first match:

      -
      rose = rose[0]
      +
      
      +```{sidebar} Querysets
      +
      +What is returned from the main search functions is actually a `queryset`. They can be treated like lists except that they can't modified in-place. We'll discuss querysets in the `next lesson` <Django-queries>`_.
       
      -

      Often you really want all matches to the search parameters you specify. In other situations, having zero or -more than one match is a sign of a problem and you need to handle this case yourself.

      -
      the_one_ring = evennia.search_object(key="The one Ring")
      -if not the_one_ring:
      -    # handle not finding the ring at all
      -elif len(the_one_ring) > 1:
      -    # handle finding more than one ring
      -else:
      -    # ok - exactly one ring found
      -    the_one_ring = the_one_ring[0]
      +

      Strings are always case-insensitive, so searching for "rose", "Rose" or "rOsE" give the same results. It’s important to remember that what is returned from these search methods is a listing of zero, one or more elements - all the matches to your search. To get the first match:

      +
      rose = roses[0]
      +
      +
      +

      Often you really want all matches to the search parameters you specify. In other situations, having zero or more than one match is a sign of a problem and you need to handle this case yourself.

      +
          the_one_ring = evennia.search_object(key="The one Ring")
      +    if not the_one_ring:
      +        # handle not finding the ring at all
      +    elif len(the_one_ring) > 1:
      +        # handle finding more than one ring
      +    else:
      +        # ok - exactly one ring found
      +        the_one_ring = the_one_ring[0]
       

      There are equivalent search functions for all the main resources. You can find a listing of them @@ -157,46 +158,54 @@ else:

      11.3. What can be searched for

      @@ -214,9 +222,9 @@ yourself and what you get back is now a list of zero, one or more matches!

    • Objects

    • Accounts

    • Scripts,

    • -
    • Channels,

    • -
    • Messages

    • -
    • Help Entries.

    • +
    • Channels

    • +
    • Messages (used by page command by default)

    • +
    • Help Entries (help entries created manually)

    Most of the time you’ll likely spend your time searching for Objects and the occasional Accounts.

    So to find an entity, what can be searched for?

    @@ -226,8 +234,7 @@ yourself and what you get back is now a list of zero, one or more matches!

    11.3.2. Search by aliases

    -

    Objects and Accounts can have any number of aliases. When searching for key these will searched too, -you can’t easily search only for aliases.

    +

    Objects and Accounts can have any number of aliases. When searching for key these will searched too, you can’t easily search only for aliases.

    rose.aliases.add("flower")
     
    @@ -236,26 +243,26 @@ you can assign new aliases to things with the

    11.3.3. Search by location

    -

    Only Objects (things inheriting from evennia.DefaultObject) has a location. This is usually a room. -The Object.search method will automatically limit it search by location, but it also works for the -general search function. If we assume room is a particular Room instance,

    +

    Only Objects (things inheriting from evennia.DefaultObject) has a location. The location is usually a room. The Object.search method will automatically limit it search by location, but it also works for the general search function. If we assume room is a particular Room instance,

    chest = evennia.search_object("Treasure chest", location=room)
     

    11.3.4. Search by Tags

    -

    Think of a Tag as the label the airport puts on your luggage when flying. -Everyone going on the same plane gets a tag grouping them together so the airport can know what should -go to which plane. Entities in Evennia can be grouped in the same way. Any number of tags can be attached +

    Think of a Tag as the label the airport puts on your luggage when flying. Everyone going on the same plane gets a tag grouping them together so the airport can know what should go to which plane. Entities in Evennia can be grouped in the same way. Any number of tags can be attached to each object.

    rose.tags.add("flowers")
    +rose.tags.add("thorny")
     daffodil.tags.add("flowers")
     tulip.tags.add("flowers")
    +cactus.tags.add("flowers")
    +cactus.tags.add("thorny")	
     

    You can now find all flowers using the search_tag function:

    all_flowers = evennia.search_tag("flowers")
    +roses_and_cactii = evennia.search_tag("thorny")
     

    Tags can also have categories. By default this category is None which is also considered a category.

    @@ -310,13 +317,15 @@ by-tag is generally faster.

    all_roses = Rose.objects.all()
     
    -

    This last way of searching is a simple form of a Django query. This is a way to express SQL queries using -Python.

    +

    This last way of searching is a simple form of a Django query. This is a way to express SQL queries using Python. See the next lesson, where we’ll explore this way to searching in more detail.

    11.3.7. Search by dbref

    -

    The database id or #dbref is unique and never-reused within each database table. In search methods you can -replace the search for key with the dbref to search for. This must be written as a string #dbref:

    + +

    The database id or #dbref is unique and never-reused within each database table. In search methods you can replace the search for key with the dbref to search for. This must be written as a string #dbref:

    the_answer = self.caller.search("#42")
     eightball = evennia.search_object("#8")
     
    @@ -325,19 +334,27 @@ eightball = evennia.search_object("#8")

    Warning

    Relying on #dbrefs

    -
    You may be used to using #dbrefs a lot from other codebases. It is however considered
    -`bad practice` in Evennia to rely on hard-coded #dbrefs. It makes your code hard to maintain
    -and tied to the exact layout of the database. In 99% of cases you should pass the actual objects
    -around and search by key/tags/attribute instead.
    -
    -
    +

    In legacy code bases you may be used to relying a lot on #dbrefs to find and track things. Looking something up by #dbref can be practical - if used occationally. It is however considered bad practice to rely on hard-coded #dbrefs in Evennia. Especially to expect end users to know them. It makes your code fragile and hard to maintain, while tying your code to the exact layout of the database. In 99% of use cases you should organize your code such that you pass the actual objects around and search by key/tags/attribute instead.

    11.4. Finding objects relative each other

    -

    Let’s consider a chest with a coin inside it. The chests stand in a room dungeon. In the dungeon is also -a door. This is an exit leading outside.

    +

    It’s important to understand how objects relate to one another when searching. +Let’s consider a chest with a coin inside it. The chests stand in a room dungeon. In the dungeon is also a door. This is an exit leading outside.

    +
    ┌───────────────────────┐
    +│dungeon                │
    +│    ┌─────────┐        │
    +│    │chest    │ ┌────┐ │
    +│    │  ┌────┐ │ │door│ │
    +│    │  │coin│ │ └────┘ │
    +│    │  └────┘ │        │
    +│    │         │        │
    +│    └─────────┘        │
    +│                       │
    +└───────────────────────┘
    +
    +
    • coin.location is chest.

    • chest.location is dungeon.

    • @@ -366,13 +383,22 @@ We can also find what is inside each object. This is a list of things.

    • door.destination is outside (or wherever the door leads)

    • room.destination is None (same for all the other non-exit objects)

    +

    You can also include this information in searches:

    +
    from evennia import search_object
    +
    +# we assume only one match of each 
    +dungeons = search_object("dungeon", typeclass="typeclasses.rooms.Room")
    +chests = search_object("chest", location=dungeons[0])
    +# find if there are any skulls in the chest 
    +skulls = search_object("Skull", candidates=chests[0].contents)
    +
    +
    +

    More advanced, nested queries like this can however often be made more efficient by using the hints in the next lesson.

    11.5. Summary

    -

    Knowing how to find things is important and the tools from this section will serve you well. For most of your needs -these tools will be all you need …

    -

    … but not always. In the next lesson we will dive further into more complex searching when we look at -Django queries and querysets in earnest.

    +

    Knowing how to find things is important and the tools from this section will serve you well. These tools will cover most of your needs …

    +

    … but not always. In the next lesson we will dive further into more complex searching when we look at Django queries and querysets in earnest.

    diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part2/Beginner-Tutorial-Game-Planning.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part2/Beginner-Tutorial-Game-Planning.html index 8a7a04dce2..13faac8169 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part2/Beginner-Tutorial-Game-Planning.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part2/Beginner-Tutorial-Game-Planning.html @@ -6,7 +6,7 @@ - On Planning a Game — Evennia 1.0-dev documentation + 2. On Planning a Game — Evennia 1.0-dev documentation @@ -17,8 +17,8 @@ - - + + @@ -64,33 +64,33 @@

    Table of Contents

      -
    • On Planning a Game
        -
      • The steps
      • -
      • Planning

        Previous topic

        Where do I begin?

        + title="previous chapter">1. Where do I begin?

        Next topic

        Planning our tutorial game

        + title="next chapter">3. Planning our tutorial game

          @@ -122,7 +122,7 @@
          -

          On Planning a Game

          +

          2. On Planning a Game

          Last lesson we asked ourselves some questions about our motivation. In this one we’ll present some more technical questions to consider. In the next lesson we’ll answer them for the sake of our tutorial game.

          @@ -143,7 +143,7 @@ later than to code in isolation until you burn out, lose interest or your hard d
        • Keep having fun. You must keep your motivation up, whichever way works for you.

        -

        The steps

        +

        2.1. The steps

        Here are the rough steps towards your goal.

        1. Planning

        2. @@ -154,7 +154,7 @@ later than to code in isolation until you burn out, lose interest or your hard d
        -

        Planning

        +

        2.2. Planning

        You need to have at least a rough idea about what you want to create. Some like a lot of planning, others do it more seat-of-the-pants style. Regardless, while some planning is always good to do, it’s common to have your plans change on you as you create your code prototypes. So don’t get too bogged down in @@ -166,7 +166,7 @@ Evennia.

        Below are some questions to get you going. In the next lesson we will try to answer them for our particular tutorial game. There are of course many more questions you could be asking yourself.

        -

        Administration

        +

        2.2.1. Administration

        • Should your game rules be enforced by coded systems or by human game masters?

        • What is the staff hierarchy in your game? Is vanilla Evennia roles enough or do you need something else?

        • @@ -174,7 +174,7 @@ tutorial game. There are of course many more questions you could be asking yours
        -

        Building

        +

        2.2.2. Building

        • How will the world be built? Traditionally (from in-game with build-commands) or externally (by batchcmds/code or directly with custom code)?

        • @@ -182,7 +182,7 @@ or directly with custom code)?

        -

        Systems

        +

        2.2.3. Systems

        • Do you base your game off an existing RPG system or make up your own?

        • What are the game mechanics? How do you decide if an action succeeds or fails?

        • @@ -194,7 +194,7 @@ or directly with custom code)?

        -

        Rooms

        +

        2.2.4. Rooms

        • Is a simple room description enough or should the description be able to change (such as with time, by light conditions, weather or season)?

        • @@ -205,7 +205,7 @@ these things something admins/game masters should handle manually?

        -

        Objects / items

        +

        2.2.5. Objects / items

        • How numerous are your objects? Do you want large loot-lists or are objects just role playing props created on demand?

        • @@ -221,7 +221,7 @@ created on demand?

        -

        Characters

        +

        2.2.6. Characters

        • Can players have more than one Character active at a time or are they allowed to multi-play?

        • How does the character-generation work? Walk from room-to-room? A menu?

        • @@ -240,7 +240,7 @@ release. Make a list. Keep future expansions in mind but limit yourself.

        -

        Coding and Tech demo

        +

        2.3. Coding and Tech demo

        This is the actual work of creating the “game” part of your game. As you code and test systems you should build a little “tech demo” along the way.

        -

        World Building

        +

        2.4. World Building

        Up until this point we’ve only had a few tech-demo objects in the database. This step is the act of populating the database with a larger, thematic world. Too many would-be developers jump to this stage too soon (skipping the Coding or even Planning stages). What if the rooms you build @@ -289,7 +289,7 @@ get a chance to hear if some things are hard to understand or non-intuitive. Ma to this feedback.

        -

        Alpha Release

        +

        2.5. Alpha Release

        As mentioned, don’t hold onto your world more than necessary. Get it out there with a huge Alpha flag and let people try it!

        Call upon your alpha-players to try everything - they will find ways to break your game in ways that @@ -303,7 +303,7 @@ game visible online.

        pre-alpha games are allowed in the index so don’t be shy)!

        -

        Beta Release/Perpetual Beta

        +

        2.6. Beta Release/Perpetual Beta

        Once things stabilize in Alpha you can move to Beta and let more people in. Many MUDs are in perpetual beta, meaning they are never considered “finished”, but just repeat the cycle of Planning, Coding, Testing and Building over and over as new @@ -311,12 +311,12 @@ features get implemented or Players come with suggestions. As the game designer to gradually perfect your vision.

        -

        Congratulate yourself!

        +

        2.7. Congratulate yourself!

        You are worthy of a celebration since at this point you have joined the small, exclusive crowd who have made their dream game a reality!

        -

        Planning our tutorial game

        +

        2.8. Planning our tutorial game

        In the next lesson we’ll make use of these general points and try to plan out our tutorial game.

        @@ -337,16 +337,16 @@ have made their dream game a reality!

        modules |
      • - next |
      • - previous |
      • - +
      develop branch
    diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part2/Beginner-Tutorial-Part2-Intro.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part2/Beginner-Tutorial-Part2-Intro.html index c486a58535..bc089b9794 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part2/Beginner-Tutorial-Part2-Intro.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part2/Beginner-Tutorial-Part2-Intro.html @@ -17,8 +17,8 @@ - - + + diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.html index efbbee4be7..2ac9e3402a 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.html @@ -6,7 +6,7 @@ - Player Characters — Evennia 1.0-dev documentation + 3. Player Characters — Evennia 1.0-dev documentation @@ -17,8 +17,8 @@ - - + + @@ -64,28 +64,28 @@

    Table of Contents

      -
    • Player Characters
        -
      • Inheritance structure
      • -
      • Living mixin class
      • -
      • Character class

        Previous topic

        Rules and dice rolling

        + title="previous chapter">2. Rules and dice rolling

        Next topic

        In-game Objects and items

        + title="next chapter">4. In-game Objects and items

          @@ -117,7 +117,7 @@
          -

          Player Characters

          +

          3. Player Characters

          In the previous lesson about rules and dice rolling we made some assumptions about the “Player Character” entity:

            @@ -129,7 +129,7 @@ should be able to be changed over time. It makes sense to base it off Evennia’ DefaultCharacter Typeclass. The Character class is like a ‘character sheet’ in a tabletop RPG, it will hold everything relevant to that PC.

            -

            Inheritance structure

            +

            3.1. Inheritance structure

            Player Characters (PCs) are not the only “living” things in our world. We also have NPCs (like shopkeepers and other friendlies) as well as monsters (mobs) that can attack us.

            In code, there are a few ways we could structure this. If NPCs/monsters were just special cases of PCs, @@ -198,7 +198,7 @@ extra functionality all living things should be able to do. This is an example o since it can also get confusing to follow the code.

            -

            Living mixin class

            +

            3.2. Living mixin class

            Create a new module mygame/evadventure/characters.py

            @@ -260,7 +260,7 @@ since it can also get confusing to follow the code.

            in the mixin means we can expect these methods to be available for all living things.

            -

            Character class

            +

            3.3. Character class

            We will now start making the basic Character class, based on what we need from Knave.

            # in mygame/evadventure/characters.py
             
            @@ -326,7 +326,7 @@ this makes it easier to handle barter and trading later.

            We implement the Player Character versions of at_defeat and at_death. We also make use of .heal() from the LivingMixin class.

            -

            Funcparser inlines

            +

            3.3.1. Funcparser inlines

            This piece of code is worth some more explanation:

            self.location.msg_contents(
                 "$You() $conj(collapse) in a heap, alive but beaten.",
            @@ -348,7 +348,7 @@ The $conj()<
             

            Note how $conj() chose collapse/collapses to make the sentences grammatically correct.

            -

            Backtracking

            +

            3.3.2. Backtracking

            We make our first use of the rules.dice roller to roll on the death table! As you may recall, in the previous lesson, we didn’t know just what to do when rolling ‘dead’ on this table. Now we know - we should be calling at_death on the character. So let’s add that where we had TODOs before:

            @@ -377,7 +377,7 @@ should be calling a
            -

            Connecting the Character with Evennia

            +

            3.4. Connecting the Character with Evennia

            You can easily make yourself an EvAdventureCharacter in-game by using the type command:

            type self = evadventure.characters.EvAdventureCharacter
            @@ -403,7 +403,7 @@ instead.

            -

            Unit Testing

            +

            3.5. Unit Testing

            Create a new module mygame/evadventure/tests/test_characters.py

            @@ -455,7 +455,7 @@ tests for other methods as practice. Refer to previous lessons for details.

          -

          About races and classes

          +

          3.6. About races and classes

          Knave doesn’t have any D&D-style classes (like Thief, Fighter etc). It also does not bother with races (like dwarves, elves etc). This makes the tutorial shorter, but you may ask yourself how you’d add these functions.

          @@ -481,7 +481,7 @@ an Attribute on your Character:

          character generation to check and include what these classes mean.

          -

          Summary

          +

          3.7. Summary

          With the EvAdventureCharacter class in place, we have a better understanding of how our PCs will look like under Knave.

          For now, we only have bits and pieces and haven’t been testing this code in-game. But if you want @@ -521,16 +521,16 @@ then on.

          modules |
        • - next |
        • - previous |
        • - - + +
        develop branch
        diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.html index 0fb1465f29..141f1255f0 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.html @@ -6,7 +6,7 @@ - Character Generation — Evennia 1.0-dev documentation + 6. Character Generation — Evennia 1.0-dev documentation @@ -17,8 +17,8 @@ - - + +
      • - next |
      • - previous |
      • - - + +
      develop branch
    @@ -64,31 +64,31 @@

    Table of Contents

    @@ -64,7 +64,7 @@

    Previous topic

    Dynamically generated Dungeon

    + title="previous chapter">11. Dynamically generated Dungeon

    Next topic

    Part 4: Using what we created

    @@ -99,7 +99,7 @@
    -

    In-game Commands

    +

    12. In-game Commands

    Warning

    This part of the Beginner tutorial is still being developed.

    @@ -125,13 +125,13 @@ next |
  • - previous |
  • - - + +
    develop branch
    diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Dungeon.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Dungeon.html index 8c95e8799e..16343a66b5 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Dungeon.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Dungeon.html @@ -6,7 +6,7 @@ - Dynamically generated Dungeon — Evennia 1.0-dev documentation + 11. Dynamically generated Dungeon — Evennia 1.0-dev documentation @@ -17,8 +17,8 @@ - - + + @@ -64,10 +64,10 @@

    Previous topic

    In-game Shops

    + title="previous chapter">10. In-game Shops

    Next topic

    In-game Commands

    + title="next chapter">12. In-game Commands

    develop branch
    diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Equipment.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Equipment.html index c3d492aa18..b94b0d0201 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Equipment.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Equipment.html @@ -6,7 +6,7 @@ - Handling Equipment — Evennia 1.0-dev documentation + 5. Handling Equipment — Evennia 1.0-dev documentation @@ -17,8 +17,8 @@ - - + + @@ -64,33 +64,33 @@

    Table of Contents

      -
    • Handling Equipment
        -
      • EquipmentHandler that saves
      • -
      • Connecting the EquipmentHandler
      • -
      • Expanding the Equipmenthandler
      • -
      • .validate_slot_usage

        Previous topic

        In-game Objects and items

        + title="previous chapter">4. In-game Objects and items

        Next topic

        Character Generation

        + title="next chapter">6. Character Generation

          @@ -122,7 +122,7 @@
          -

          Handling Equipment

          +

          5. Handling Equipment

          In Knave, you have a certain number of inventory “slots”. The amount of slots is given by CON + 10. All items (except coins) have a size, indicating how many slots it uses. You can’t carry more items than you have slot-space for. Also items wielded or worn count towards the slots.

          @@ -147,7 +147,7 @@ they can do. The shield, helmet and armor they use affects their defense.

          Basically, all the weapon/armor locations are exclusive - you can only have one item in each (or none). The BACKPACK is special - it contains any number of items (up to the maximum slot usage).

          -

          EquipmentHandler that saves

          +

          5.1. EquipmentHandler that saves

          Create a new module mygame/evadventure/equipment.py.

          @@ -233,7 +233,7 @@ other Attributes.

          have one item except WieldLocation.BACKPACK, which is a list.

          -

          Connecting the EquipmentHandler

          +

          5.2. Connecting the EquipmentHandler

          Whenever an object leaves from one location to the next, Evennia will call a set of hooks (methods) on the object that moves, on the source-location and on its destination. This is the same for all moving things - whether it’s a character moving between rooms or an item being dropping from your hand to the ground.

          @@ -297,10 +297,10 @@ we will skip that for this tutorial.

          we can use this, we need to go actually adding those methods.

          -

          Expanding the Equipmenthandler

          +

          5.3. Expanding the Equipmenthandler

          -

          .validate_slot_usage

          +

          5.4. .validate_slot_usage

          Let’s start with implementing the first method we came up with above, validate_slot_usage:

          # mygame/evadventure/equipment.py 
           
          @@ -356,7 +356,7 @@ little less to type.

          We add two helpers - the max_slots property and count_slots, a method that calculate the current slots being in use. Let’s figure out how they work.

          -

          .max_slots

          +

          5.4.1. .max_slots

          For max_slots, remember that .obj on the handler is a back-reference to the EvAdventureCharacter we put this handler on. getattr is a Python method for retrieving a named property on an object. The Enum Ability.CON.value is the string Constitution (check out the @@ -378,7 +378,7 @@ should happen to not be a property “Constitution” on -

          .count_slots

          +

          5.4.2. .count_slots

          In this helper we use two Python tools - the sum() function and a list comprehension. The former simply adds the values of any iterable together. The latter is a more efficient way to create a list:

          @@ -416,14 +416,14 @@ sizes”.

          together.

          -

          Validating slots

          +

          5.4.3. Validating slots

          With these helpers in place, validate_slot_usage now becomes simple. We use max_slots to see how much we can carry. We then get how many slots we are already using (with count_slots) and see if our new obj’s size would be too much for us.

          -

          .add and .remove

          +

          5.5. .add and .remove

          We will make it so .add puts something in the BACKPACK location and remove drops it, wherever it is (even if it was in your hands).

          # mygame/evadventure/equipment.py 
          @@ -472,7 +472,7 @@ return all items.

          be lost after a server reload.

          -

          Moving things around

          +

          5.6. Moving things around

          With the help of .remove() and .add() we can get things in and out of the BACKPACK equipment location. We also need to grab stuff from the backpack and wield or wear it. We add a .move method on the EquipmentHandler to do this:

          @@ -527,7 +527,7 @@ it goes. So we just need to move the object to that slot, replacing whatever is from before. Anything we replace goes back to the backpack.

          -

          Get everything

          +

          5.7. Get everything

          In order to visualize our inventory, we need some method to get everything we are carrying.

          # mygame/evadventure/equipment.py 
           
          @@ -558,7 +558,7 @@ from before. Anything we replace goes back to the backpack.

          [(item, WieldLocation), ...]. This is convenient for display.

          -

          Weapon and armor

          +

          5.8. Weapon and armor

          It’s convenient to have the EquipmentHandler easily tell you what weapon is currently wielded and what armor level all worn equipment provides. Otherwise you’d need to figure out what item is in which wield-slot and to add up armor slots manually every time you need to know.

          @@ -609,7 +609,7 @@ object that represents your bare hands with damage and all. (created in The Object tutorial earlier).

          -

          Extra credits

          +

          5.9. Extra credits

          This covers the basic functionality of the equipment handler. There are other useful methods that can be added:

          -

          Unit Testing

          +

          5.10. Unit Testing

          Create a new module mygame/evadventure/tests/test_equipment.py.

          @@ -663,7 +663,7 @@ passing these into the handler’s methods.

    -

    Summary

    +

    5.11. Summary

    Handlers are useful for grouping functionality together. Now that we spent our time making the EquipmentHandler, we shouldn’t need to worry about item-slots anymore - the handler ‘handles’ all the details for us. As long as we call its methods, the details can be forgotten about.

    @@ -689,16 +689,16 @@ generation - where players get to make their own character!

    modules |
  • - next |
  • - previous |
  • - - + +
    develop branch
    diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-NPCs.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-NPCs.html index 4187942783..f9954cdcc7 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-NPCs.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-NPCs.html @@ -6,7 +6,7 @@ - Non-Player-Characters (NPCs) — Evennia 1.0-dev documentation + 8. Non-Player-Characters (NPCs) — Evennia 1.0-dev documentation @@ -17,8 +17,8 @@ - - + + @@ -64,10 +64,10 @@

    Previous topic

    In-game Rooms

    + title="previous chapter">7. In-game Rooms

    Next topic

    Game Quests

    + title="next chapter">9. Game Quests

    develop branch
    diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Objects.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Objects.html index a69c9bda1d..0a5b09863e 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Objects.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Objects.html @@ -6,7 +6,7 @@ - In-game Objects and items — Evennia 1.0-dev documentation + 4. In-game Objects and items — Evennia 1.0-dev documentation @@ -17,8 +17,8 @@ - - + + @@ -64,30 +64,30 @@

    Table of Contents

      -
    • In-game Objects and items
        -
      • New Enums
      • -
      • The base object

        Previous topic

        Player Characters

        + title="previous chapter">3. Player Characters

        Next topic

        Handling Equipment

        + title="next chapter">5. Handling Equipment

          @@ -119,7 +119,7 @@
          -

          In-game Objects and items

          +

          4. In-game Objects and items

          In the previous lesson we established what a ‘Character’ is in our game. Before we continue we also need to have a notion what an ‘item’ or ‘object’ is.

          Looking at Knave’s item lists, we can get some ideas of what we need to track:

          @@ -132,7 +132,7 @@ only belong in the backpack.

        • obj_type - Which ‘type’ of item this is.

        -

        New Enums

        +

        4.1. New Enums

        We added a few enumberations for Abilities back in the Utilities tutorial. Before we continue, let’s expand with enums for use-slots and object types.

        # mygame/evadventure/enums.py
        @@ -164,7 +164,7 @@ Before we continue, let’s expand with enums for use-slots and object types.

        Once we have these enums, we will use them for referencing things.

        -

        The base object

        +

        4.2. The base object

        Create a new module mygame/evadventure/objects.py

        @@ -209,7 +209,7 @@ child classes to represent the relevant types:

        -

        Using Attributes or not

        +

        4.2.1. Using Attributes or not

        In theory, size and value does not change and could also be just set as a regular Python property on the class:

        class EvAdventureObject(DefaultObject):
        @@ -237,7 +237,7 @@ the database for all objects with 
         
        -

        Creating tags in at_object_creation

        +

        4.2.2. Creating tags in at_object_creation

        The at_object_creation is a method Evennia calls on every child of DefaultObject whenever it is first created.

        We do a tricky thing here, converting our .obj_type to one or more Tags. Tagging the @@ -256,7 +256,7 @@ is also Magical, for example.

        -

        Other object types

        +

        4.3. Other object types

        Some of the other object types are very simple so far.

        # mygame/evadventure/objects.py 
         
        @@ -280,7 +280,7 @@ is also Magical, for example.

        -

        Consumables

        +

        4.4. Consumables

        A ‘consumable’ is an item that has a certain number of ‘uses’. Once fully consumed, it can’t be used anymore. An example would be a health potion.

        # mygame/evadventure/objects.py 
        @@ -315,7 +315,7 @@ anymore. An example would be a health potion.

        later, overriding at_use with different effects.

        -

        Weapons

        +

        4.5. Weapons

        All weapons need properties that describe how efficient they are in battle.

        # mygame/evadventure/objects.py 
         
        @@ -341,7 +341,7 @@ a weapon’s quality will go down. When it reaches 0, it will break.

        The attack/defend type tracks how we resolve attacks with the weapon, like roll + STR vs ARMOR + 10.

        -

        Magic

        +

        4.6. Magic

        In Knave, anyone can use magic if they are wielding a rune stone (our name for spell books) in both hands. You can only use a rune stone once per rest. So a rune stone is an example of a ‘magical weapon’ that is also a ‘consumable’ of sorts.

        @@ -388,7 +388,7 @@ base class. Since magic in Knave tends to be pretty custom, it makes se of custom code.

        -

        Armor

        +

        4.7. Armor

        Armor, shields and helmets increase the ARMOR stat of the character. In Knave, what is stored is the defense value of the armor (values 11-20). We will instead store the ‘armor bonus’ (1-10). As we know, defending is always bonus + 10, so the result will be the same - this means @@ -418,7 +418,7 @@ we can use Ability.

        -

        Your Bare hands

        +

        4.8. Your Bare hands

        This is a ‘dummy’ object that is not stored in the database. We will use this in the upcoming Equipment tutorial lesson to represent when you have ‘nothing’ in your hands. This way we don’t need to add any special case for this.

        @@ -437,7 +437,7 @@ in your hands. This way we don’t need to add any special case for this.

        -

        Testing and Extra credits

        +

        4.9. Testing and Extra credits

        Remember the get_obj_stats function from the Utility Tutorial earlier? We had to use dummy-values since we didn’t yet know how we would store properties on Objects in the game.

        Well, we just figured out all we need! You can go back and update get_obj_stats to properly read the data @@ -465,16 +465,16 @@ to get_obj_statsmodules |

      • - next |
      • - previous |
      • - - + +
      develop branch
      diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Part3-Intro.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Part3-Intro.html index 82bd05b87f..7736b871dd 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Part3-Intro.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Part3-Intro.html @@ -6,7 +6,7 @@ - Part 3: How we get there — Evennia 1.0-dev documentation + Part 3: How we get there (example game) — Evennia 1.0-dev documentation @@ -17,8 +17,8 @@ - - + +
    • - next |
    • - previous |
    • - +
    develop branch
    @@ -63,19 +63,18 @@

    Table of Contents

    develop branch
    @@ -64,10 +64,10 @@

    Previous topic

    Non-Player-Characters (NPCs)

    + title="previous chapter">8. Non-Player-Characters (NPCs)

    Next topic

    In-game Shops

    + title="next chapter">10. In-game Shops

    develop branch
    diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Rooms.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Rooms.html index 8d7835f652..f6167d6e1c 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Rooms.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Rooms.html @@ -6,7 +6,7 @@ - In-game Rooms — Evennia 1.0-dev documentation + 7. In-game Rooms — Evennia 1.0-dev documentation @@ -17,8 +17,8 @@ - - + + @@ -64,10 +64,10 @@

    Previous topic

    Character Generation

    + title="previous chapter">6. Character Generation

    Next topic

    Non-Player-Characters (NPCs)

    + title="next chapter">8. Non-Player-Characters (NPCs)

    develop branch
    diff --git a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Rules.html b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Rules.html index 2e65d8520f..758f133a17 100644 --- a/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Rules.html +++ b/docs/1.0-dev/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Rules.html @@ -6,7 +6,7 @@ - Rules and dice rolling — Evennia 1.0-dev documentation + 2. Rules and dice rolling — Evennia 1.0-dev documentation @@ -17,8 +17,8 @@ - - + + @@ -64,35 +64,35 @@

    Table of Contents

    develop branch
    @@ -64,25 +64,25 @@

    Table of Contents

    Previous topic

    -

    Web Character View Tutorial

    +

    Help System Tutorial

    Next topic

    -

    Understanding Color Tags

    @@ -835,10 +835,10 @@ code.

    modules |
  • - next |
  • - previous |
  • diff --git a/docs/1.0-dev/Howtos/Turn-based-Combat-System.html b/docs/1.0-dev/Howtos/Turn-based-Combat-System.html index f0c54c8f50..5aa3c07e6b 100644 --- a/docs/1.0-dev/Howtos/Turn-based-Combat-System.html +++ b/docs/1.0-dev/Howtos/Turn-based-Combat-System.html @@ -17,7 +17,7 @@ - + -

    The permission functions can check anything you like on the accessing user, so long as the function -returns either True (they’re allowed) or False (they’re not).

    +

    The permission functions can check anything you like on the accessing user, so long as the function returns either True (they’re allowed) or False (they’re not).

    For a full list of possible settings, you can check out the django-wiki documentation.

    @@ -334,7 +331,7 @@ returns either True (they’re allowed) or False (they’re not).

    next |
  • - previous |
  • diff --git a/docs/1.0-dev/Howtos/Web-Character-Generation.html b/docs/1.0-dev/Howtos/Web-Character-Generation.html index b36f70fe8d..bec02c6164 100644 --- a/docs/1.0-dev/Howtos/Web-Character-Generation.html +++ b/docs/1.0-dev/Howtos/Web-Character-Generation.html @@ -18,7 +18,7 @@ - +