ID:142034
 
Code:Deadron's Characterhandler.dm and Implementation.dm
#include "implementation.dm"

/////////////////////////////////////////////
// WELCOME TO BASECAMP: CHARACTER HANDLING //
/////////////////////////////////////////////
/*
For a fully working example using this library, download:

byond://Deadron.SimpleSaving

or

byond://Deadron.CharacterSaving

Everything you need to read about the library is in this file.
You don't need to look at anything else in the library, unless
you have a good understanding of BYOND and want to see how it
works.
*/



/****
USING THE LIBRARY
To use the library, you only need to include it, with this line:

#include <deadron/characterhandling>

You don't need to copy/paste any library code or anything.
By including it, it will automatically add itself to your game.

You don't need to do anything else special to use the library.
The only requirement is the same as for any BYOND game:
Specify the mob type for new mobs in world.mob, as discussed
in detail below.

What it does
------------
When a player logs in, the library checks to see if they have
any characters saved. If they do, and if you only allow for
one character, then the player is immediately logged into that
character and all their attributes and inventory are restored.

When the player logs out, their character is automatically saved.
The character is saved based on its name.


Choosing a character
--------------------
If you allow for multiple characters, then the library lets the
player choose which character they want to play, or to create
or delete a character. When the player chooses a character,
they are logged into it. By default he library handles the choosing
process automatically. You don't need to do anything.

You can customize the menus used for choosing and deleting characters,
as discussed below.

If you want to change the default behavior, look at the client
variables discussed further down in this file.


Creating a character
--------------------
If a character needs to be created, then the library creates
a mob of type world.mob and logs the character into it.
Whatever you specify as world.mob is what is created.
For example, if this is how world.mob is set:

world
mob = /mob/new_character

Then when a brand new character is created, it will be of type
/mob/new_character. You can make this any class you want, and
can do anything you want with the class. The library just creates
a mob of that class and logs the player into it.

If you want the player to specify attributes when their character
is created, then you might have a mob class designed just to
ask them what kind of character they want. See the CharacterSaving
demo for a complete example of this.

***/



/*
Setting the number of characters
--------------------------------
You can specify how many characters a player is allowed to have.
using the client variable, base_num_characters_allowed.

If you set it to 1, then players will be immediately logged into
their one character. If you allow more than one, then players will
automatically be given a choice of which character to they want to play,
as well as options for creating and deleting characters.
*/

client/base_num_characters_allowed = 1


/*
Automatically loading/saving player characters
---------------------------------------
By default, CharacterHandling will automatically load the player's
character on login, and save a player's character and delete the mob
on logout. If you don't want one or more of these to happen by default,
then set the appropriate "auto" variable(s) to 0.

If you don't want the character autoloaded, call the player's
client.base_ChooseCharacter() when you DO want it loaded.
*/

client/base_autoload_character = 1
client/base_autosave_character = 1
client/base_autodelete_mob = 1

/*
Saving verbs
------------
BYOND does not save verbs, but the library takes care of this for you
by default. If you don't want verbs saved, then set this to 0 in
your code.
*/

client/base_save_verbs = 1


client/base_ChooseCharacter()
/*
Choosing a character
--------------------
This function is called automatically on login if
client.base_autoload_character is 1. If only one
character is allowed, it immediately logs the player
into a character. If multiple characters are allowed,
it gives the player a menu to create/choose/delete
characters.
*/

return ..()


mob/BaseCamp/ChoosingCharacter
ChooseCharacterMenu(list/menu)
/*
Customizing Choose menu
-----------------------
This function receives the list of menu items for choosing
a character. It can display these any way it wishes,
then send the player's choice to the ChooseCharacterResult()
proc. The default behavior uses an ugly input() list.
*/

return ..()

DeleteCharacterMenu(list/menu)
/*
Customizing Delete menu
-----------------------
This function receives the list of menu items for deleting
a character. It can display these any way it wishes,
then send the player's choice to the DeleteCharacterResult()
proc. The default behavior uses an ugly input() list.
*/

return ..()

client/base_SaveMob()
/*
Forcing the character to be saved
---------------------------------
Characters are saved automatically when the player logs out.
If you want them saved at other times too, just call this
client function for the mob when you want it saved:
*/

return ..()



mob/BaseCamp/FirstTimePlayer
FirstTimePlayer()
/*
Handling first time players
---------------------------
If you want to do something special the first time a player ever logs
into your game, you can do so by putting code in the FirstTimePlayer
class, FirstTimePlayer() proc. This is only the first time EVER that
they login...it is not called everytime they login.

You can use this to charge the player, or get their email address,
or give them special help or whatever.

If you don't want to use this, then don't do anything. By default,
nothing will happen. This is ONLY called the very first time a
player logs in, so it's not useful for things that check everytime
the player logs in.
*/

return 1


/*
If you don't want a mob to be saved, set
mob/base_save_allowed to 0.
*/

mob/base_save_allowed = 1


/*
If you don't want the mob's location to be saved and restored,
set mob/base_save_location to 0.
*/

mob/base_save_location = 1


mob/base_InitFromSavefile()
/*
Initializing from savefile
--------------------------
Sometimes you have special checks you need to do or things you need
to add when a character is read in from the savefile. If so,
you can do them in this mob function:

By default nothing happens. This is just here in case you need it.
*/

return


////////////////////////////
// DON'T TOUCH THIS FILE! //
////////////////////////////
/*
Unless you are a BYOND guru who is curious how this works,
there is no point in reading this file. It's just the gritty
code, and you NEVER need to touch it or even read it.

See characterhandling.dm for full library documentation.

If you touch this file and then your character saving breaks,
don't bug me about it, it's not my problem.
*/


#include <deadron/basecamp>

#define BASE_MENU_CREATE_CHARACTER "Create New Character"
#define BASE_MENU_DELETE_CHARACTER "Delete Character"
#define BASE_MENU_CANCEL "Cancel"
#define BASE_MENU_QUIT "Quit"


// Implementation
client/var/tmp
base_num_characters_allowed = 1
base_autoload_character = 1
base_autosave_character = 1
base_autodelete_mob = 1
base_save_verbs = 1

mob
var/tmp
base_save_allowed = 1 // Is this mob allowed to be saved?
base_save_location = 1 // Should the mob's location be saved and restored?

var/list/base_saved_verbs // If we're saving verbs they are stored here.

proc/base_InitFromSavefile()
return

Write(savefile/F)
// Save the location if that has been specified and there is a map.
// First, call the default Write() behavior for mobs.
..()

if (base_save_location && world.maxx)
F["last_x"] << x
F["last_y"] << y
F["last_z"] << z
return

Read(savefile/F)
// Restore the mob's location if that has been specified and there is a map.
// Tries to use Move() to place the character, in case the game has special Move() handling.
// If that fails, forces the move by setting the loc.

// Call the default Read() behavior for mobs.
..()

if (base_save_location && world.maxx)
var/last_x
var/last_y
var/last_z
F["last_x"] >> last_x
F["last_y"] >> last_y
F["last_z"] >> last_z
var/destination = locate(last_x, last_y, last_z)

if (!Move(destination))
loc = destination
return

mob/BaseCamp
base_save_allowed = 0 // BaseCamp mobs are for admin only.

Login()
RemoveVerbs()
// Don't use any other Login() code.
return

Stat()
// Don't want any stat panels for BaseCamp mobs.
return

Logout()
// Don't use any other Logout() code.
return

proc/RemoveVerbs()
for (var/my_verb in verbs)
verbs -= my_verb

mob/BaseCamp/FirstTimePlayer
proc/FirstTimePlayer()
// Here in case developer wants to do something first time a player ever logs in.
return 1


mob/BaseCamp/ChoosingCharacter
Login()
// spawn to make sure all administrative tasks are over.
spawn()
ChooseCharacter()
return ..()

proc/ChooseCharacter()
var/list/available_char_names = client.base_CharacterNames()
var/list/menu = new()
menu += available_char_names

if (length(available_char_names) < client.base_num_characters_allowed)
if (client.base_num_characters_allowed == 1)
// If only one character allowed, jump right to creating character.
client.base_NewMob()
del(src)
return
else
menu += BASE_MENU_CREATE_CHARACTER

if (length(available_char_names))
menu += BASE_MENU_DELETE_CHARACTER

menu += BASE_MENU_QUIT

// Let developer provide their own menu if they wish.
ChooseCharacterMenu(menu)

proc/ChooseCharacterMenu(list/menu)
// Given a list of menu options, display them and call ChooseCharacterResult() with the choice.
var/default = null
var/result = input(src, "Who do you want to be this fine day?", "Welcome to [world.name]!", default) in menu
ChooseCharacterResult(result)

proc/ChooseCharacterResult(menu_choice)
// Respond to the option the player chose from the character choosing menu.
switch(menu_choice)
if (0, BASE_MENU_QUIT)
// Kick them off.
del(src)
return

if (BASE_MENU_CREATE_CHARACTER)
client.base_NewMob()
del(src)
return

if (BASE_MENU_DELETE_CHARACTER)
// Give them a chance to delete something, but then they need to choose.
DeleteCharacter()
return

// They must have chosen a character, so load it.
var/mob/Mob = client.base_LoadMob(menu_choice)
if (Mob)
del(src)
else
ChooseCharacter()

proc/DeleteCharacter()
var/list/available_char_names = client.base_CharacterNames()
var/list/menu = new()
menu += available_char_names

menu += BASE_MENU_CANCEL
menu += BASE_MENU_QUIT

// Let developer provide their own menu if they wish.
DeleteCharacterMenu(menu)

proc/DeleteCharacterMenu(list/menu)
// Given a list of menu options, display them and return the result.
var/default = null
var/result = input(src, "Which character do you want to delete?", "Deleting character", default) in menu
if (result)
DeleteCharacterResult(result)

proc/DeleteCharacterResult(menu_choice)
// Respond to the option the player chose from the character deletion menu.
switch(menu_choice)
if (0, BASE_MENU_QUIT)
// Kick them off.
del(src)
return

if (BASE_MENU_CANCEL)
ChooseCharacter()
return

// They chose a character to delete.
client.base_DeleteMob(menu_choice)
ChooseCharacter()
return


client
var/tmp/savefile/_base_player_savefile

New()
// Let them choose/create a character.
if (base_autoload_character)
base_ChooseCharacter()
base_Initialize()
return
return ..()

Del()
// Save character.
if (base_autosave_character)
base_SaveMob()

// Delete mob.
if (base_autodelete_mob)
del(mob)
return ..()


proc/base_PlayerSavefile()
if (!_base_player_savefile)
// Put in players/[first_initial]/[ckey].sav
var/start = 1
var/end = 2
var/first_initial = copytext(ckey, start, end)
var/filename = "players/[first_initial]/[ckey].sav"
_base_player_savefile = new(filename)
return _base_player_savefile


proc/base_FirstTimePlayer()
var/mob/BaseCamp/FirstTimePlayer/first_mob = new()
first_mob.name = key
first_mob.gender = gender
mob = first_mob
return first_mob.FirstTimePlayer()


proc/base_ChooseCharacter()
// Switches the player to a choosing character mob.
// In case switching in middle of game, save previous mob.
base_SaveMob()

var/mob/BaseCamp/ChoosingCharacter/chooser

// Do they have any characters to choose from?
var/list/names = base_CharacterNames()
if (!length(names))
// They must be a first time player.
var/result = base_FirstTimePlayer()
if (!result)
// They weren't approved, so boot 'em.
del(src)
return

// Okay let them create their first character.
chooser = new()
mob = chooser
return

// If only one character is allowed, try to just load it.
if (base_num_characters_allowed == 1)
base_LoadMob(names[1])
return

chooser = new()
mob = chooser
return


proc/base_CharacterNames()
// Get the full names of all this player's characters.
var/list/names = new()
var/savefile/F = base_PlayerSavefile()

F.cd = "/players/[ckey]/mobs/"
var/list/characters = F.dir
var/char_name
for (var/entry in characters)
F["[entry]/name"] >> char_name
names += char_name
return names


proc/base_NewMob()
// Give the player a standard mob.
// First save existing mob if necessary.
base_SaveMob()
var/mob/new_mob
new_mob = new world.mob()
new_mob.name = key
new_mob.gender = gender
mob = new_mob
// new_mob.base_Initialize()

// Clear out the savefile to keep it from staying open.
_base_player_savefile = null
return new_mob


proc/base_SaveMob()
// Saves the player's current mob based on the ckey of its name.
if (!mob || !mob.base_save_allowed)
return

// If we're saving verbs, move them over now.
if (base_save_verbs)
mob.base_saved_verbs = mob.verbs

var/savefile/F = base_PlayerSavefile()

var/mob_ckey = ckey(mob.name)

var/directory = "/players/[ckey]/mobs/[mob_ckey]"
F.cd = directory
F["name"] << mob.name
F["mob"] << mob
_base_player_savefile = null


proc/base_LoadMob(char_name)
// Look for a character with the ckey version of this name.
// If found, load it, set the client mob to it, and return it.
// Otherwise return null.
var/mob/new_mob
var/char_ckey = ckey(char_name)
var/savefile/F = base_PlayerSavefile()
_base_player_savefile = null

F.cd = "/players/[ckey]/mobs/"
var/list/characters = F.dir
if (!characters.Find(char_ckey))
world.log << "[key]'s client.LoadCharacter() could not locate character [char_name]."
mob << "Unable to load [char_name]."
return null

F["[char_ckey]/mob"] >> new_mob
if (new_mob)
mob = new_mob
new_mob.base_InitFromSavefile()

// If we're doing verbs, set them now.
if (base_save_verbs && new_mob.base_saved_verbs)
for (var/item in new_mob.base_saved_verbs)
new_mob.verbs += item
return new_mob
return null


proc/base_DeleteMob(char_name)
// Look for a character with the ckey version of this name.
// If found, delete it.
var/char_ckey = ckey(char_name)
var/savefile/F = base_PlayerSavefile()

F.cd = "/players/[ckey]/mobs/"
F.dir.Remove(char_ckey)


Problem description:
I added this file to a game and then when i log it it say:
runtime error: Cannot write to atom.verbs.
proc name: Login (/mob/Login)
usr: NazoXIII (/mob/male1)
src: NazoXIII (/mob/male1)
call stack:
NazoXIII (/mob/male1): Login()
NazoXIII (/mob/male1): Login()
NazoXIII (/mob/male1): Login()
NazoXIII (/mob/male1): Read(players/d/deekid14.sav (/savefile))
Deekid14 (/client): base LoadMob("NazoXIII")
ChoosingCharacter (/mob/BaseCamp/ChoosingCharacter): ChooseCharacterResult("NazoXIII")
ChoosingCharacter (/mob/BaseCamp/ChoosingCharacter): ChooseCharacterMenu(/list (/list))
ChoosingCharacter (/mob/BaseCamp/ChoosingCharacter): ChooseCharacterMenu(/list (/list))
ChoosingCharacter (/mob/BaseCamp/ChoosingCharacter): ChooseCharacter()
ChoosingCharacter (/mob/BaseCamp/ChoosingCharacter): Login()

Is that common? or is there a fix or a better character handeling System
It means you're messing something up when trying to modify his code. I can assure you that no one is going to look through that entire file to find your problem.