PHAT-CLIENT TUTORIAL
The following is an assortment of code we wrote to support the development of an RPG style gaming environment in MUE/MOO. PLEASE REALIZE that it is intended primarily for programmers who want to incorporate RPG gaming elements into their own MOOs, perhaps even in order to interface to the MAM framework, though of course this integration is not required to find the code useful. Hopefully you have already taken a moment to look at the general MUE/MOO introduction information, the combat system to get a feel for the game elements, as well as the MUE/MOO and MAM interfaces to get a sense of how XML messages get passed between MOO and MAM.
We needed to custom code the gaming environment into MOO because it grew out of earlier MUD systems that were geared more specifically toward hack and slash adventuring scenarios. Many wanted to take advantage of the extensible, real-time multiuser, chat-based functionality afforded by MUDs, but in the context of business, education, and research. This desire caused a lot of the game stuff to either be stripped out or left underdeveloped in various MOO server distributions (such as v1.1 the High Wired enCore implementation of LambdaMOO that we're using). We wanted to reintroduce the gaming elements, so you could experience the best and worst of both worlds. Undoubtedly, a lot of stuff could be done better. But hey, it's just a beginning, and it works for the most part.
NOTE: This tutorial assumes general understanding of how to program in MOO. If you lack this knowledge, you may want to refer to the following reference materials. It also assumes that you realize the code is provided primarily for reference purposes, and local modifications would need to be made to implement it in other environments. Of course, you are free to cut and paste as you see fit.
QUICK LINKS: MUE/MOO Game and Utility Code
- Player Verbs and Properties
- Monster Verbs and Properties
- Barter Bot Verbs and Properties
- Space Verbs and Properties
- Utility Verbs
- Phat-Client Tutorial: Menuing and Functionality
- Phat-Client Tutorial: Agent Attributes
- Phat-Client Tutorial: Custom Look & Feel
- Phat-Client Tutorial: [for programmers] Packages and Agent Types
- Phat-Client Tutorial: [for programmers] Messaging and Agent Layers
- Phat-Client Tutorial: [for programmers] MUE/MOO and MAM interfaces
- Phat-Client Tutorial: [for programmers] MUE/MOO Game and Utility Code
- Phat-Client Tutorial: [for programmers] Server-Side Scripts
- Phat-Client Tutorial: [for programmers] MAM UML Documentation
- Phat-Client Tutorial: [for programmers] MAM Javadocs
- PROXY portal
- Monster Verbs and Properties
PLAYER VERBS AND PROPERTIES
The first thing we needed to do was give the $player class (obj #6 in this case) a few new verbs and properties. The verbs include code for: generating readout screens of current status, skills, experience points, and doing player comparisons; handling hitpoint checks, leveling, and funds transfers; and utilizing combat tactics and tongues in relation to other players and monsters.
PLAYER VERBS
The first verb we'll look at creates a skills table. It displays competency in the various tactics and tongues deployed in combat. To see an example of what it looks like in use, check out the player skills screen in the combat system overview.
-------------------------------------------------------------------- $player:sk*ills none none none permissions: rd col_width=68; player:tell(""); player:tell("Skills for ", player.name, ":"); player:tell("------------------------------Tactics-------------------------------"); row1 = {{"tactic 1", "left", 8},{$string_utils:to_percent(player.tactic1),"right",5},{"","",10},{"tactic 2","left",13},{$string_utils:to_percent(player.tactic2),"right",5},{"","",10},{"tactic 3","left",12},{$string_utils:to_percent(player.tactic3),"right",5}}; player:tell($string_utils:flex_col_format(row1)); player:tell("------------------------------Tongues-------------------------------"); row2 = {{"tongue 1", "left", 8},{$string_utils:to_percent(player.tongue1),"right",5},{"","",10},{"tongue 2","left",13},{$string_utils:to_percent(player.tongue2),"right",5},{"","",10},{"tongue 3","left",12},{$string_utils:to_percent(player.tongue3),"right",5}}; player:tell($string_utils:flex_col_format(row2)); player:tell($string_utils:space(col_width, "-")); player:tell($string_utils:format_two_col("Practice sessions attended: " + $string_utils:from_value(player.practice_sessions_attended), "Practice sessions remaining: " + $string_utils:from_value(player.practice_sessions_remaining),col_width)); endif .
This verb creates an experience point table. It displays the required points for the various levels, the player's current level, and the needed points to advance. To see an example of what it looks like in use, check out the player experience screen in the combat system overview.
-------------------------------------------------------------------- $player:ex*perience none none none permissions: rd total_exp_levels = length(player.exp_table); player:tell(""); player:tell(("Experience required, levels 2 to " + $string_utils:from_value(total_exp_levels + 1)) + ":"); player:tell("-------------------------------------------------"); current_exp = $string_utils:group_number(player.exp) + ")"; needed_exp = 0; if (player.lev <= total_exp_levels) needed_exp = $string_utils:group_number(player.exp_table[player.lev]) + ")"; endif exp_table = {{"( 2)", $string_utils:group_number(player.exp_table[1]) + " exp"}}; exp_table = listappend(exp_table, {"( 3)", $string_utils:group_number(player.exp_table[2]) + " exp"}); levels_after_2 = player.exp_table[3..total_exp_levels]; level_id = 3; for exp_for_level in (levels_after_2) level_id = level_id + 1; exp_table = listappend(exp_table, {("( " + $string_utils:from_value(level_id)) + ")", $string_utils:group_number(exp_for_level) + " exp"}); endfor for exp_line in (exp_table) {exp_level, exp_required_for_level} = exp_line; if (exp_level == "( 2)") s = $string_utils:flex_col_format({{exp_level, "", 8}, {exp_required_for_level, "right", 15}, {"", "", 2}, {"(Current:", "", 10}, {current_exp, "right", 14}}); elseif (exp_level == "( 3)") if (player.lev <= total_exp_levels) s = $string_utils:flex_col_format({{exp_level, "", 8}, {exp_required_for_level, "right", 15}, {"", "", 2}, {"(Needed:", "", 10}, {needed_exp, "right", 14}}); else s = $string_utils:flex_col_format({{exp_level, "", 8}, {exp_required_for_level, "right", 15}, {"", "", 2}, {"(Needed:", "", 10}, {"0)", "right", 14}}); endif else s = $string_utils:flex_col_format({{exp_level, "", 8}, {exp_required_for_level, "right", 15}}); endif player:tell(s); endfor player:tell("-------------------------------------------------"); if ($object_utils:isa(player, $guest)) player:tell("Sorry ", this.name, ", but unless you register you will be unable to gain experience points and advance levels..."); return; endif .
The next verb shows most of the player data. The code starts by determining what rank to assign the player's alienation, ambition, and anxiety attributes. It then uses a custom two column table layout (see the utilities section below) for displaying statistics on funds, player characteristics, login times, hitpoints, combat wins and losses, and so on. To see an example of what it looks like in use, check out the player stats screen in the combat system overview.
-------------------------------------------------------------------- $player:st*ats none none none permissions: rd col_width = 68; quota_total = $quota_utils:get_quota(player); quota_used = $quota_utils:summarize_one_user(player)[1]; if (player.ali <= 0.4) ali_rank = "very low"; elseif ((player.ali >= 0.5) && (player.ali < 1.5)) ali_rank = "low"; elseif ((player.ali >= 1.5) && (player.ali < 2.5)) ali_rank = "moderate"; elseif ((player.ali >= 2.5) && (player.ali < 3.5)) ali_rank = "high"; elseif (player.ali >= 3.5) ali_rank = "very high"; endif if (player.amb <= 0.4) amb_rank = "very low"; elseif ((player.amb >= 0.5) && (player.amb < 1.5)) amb_rank = "low"; elseif ((player.amb >= 1.5) && (player.amb < 2.5)) amb_rank = "moderate"; elseif ((player.amb >= 2.5) && (player.amb < 3.5)) amb_rank = "high"; elseif (player.amb >= 3.5) amb_rank = "very high"; endif if (player.anx <= 0.4) anx_rank = "very low"; elseif ((player.anx >= 0.5) && (player.anx < 1.5)) anx_rank = "low"; elseif ((player.anx >= 1.5) && (player.anx < 2.5)) anx_rank = "moderate"; elseif ((player.anx >= 2.5) && (player.anx < 3.5)) anx_rank = "high"; elseif (player.anx >= 3.5) anx_rank = "very high"; endif player:tell(""); player:tell($string_utils:format_two_col(("Stats for " + player.name) + ":", "Funds: " + $string_utils:format_funds(player.funds), col_width)); player:tell($string_utils:space(col_width, "-")); row1 = {{"ALI: ", "left", 5}, {$string_utils:from_value($math_utils:trunc(player.ali)), "left", 3}, {"/", "", 1}, {$string_utils:from_value(player.ali_max), "", 4}, {("[" + ali_rank) + "]", "left", 13}, {"Login: " + ctime(time() - connected_seconds(player)), "right", 42}}; player:tell($string_utils:flex_col_format(row1)); row2 = {{"AMB: ", "left", 5}, {$string_utils:from_value($math_utils:trunc(player.amb)), "left", 3}, {"/", "", 1}, {$string_utils:from_value(player.amb_max), "", 4}, {("[" + amb_rank) + "]", "left", 13}, {"Time: " + ctime(), "right", 42}}; player:tell($string_utils:flex_col_format(row2)); row3 = {{"ANX: ", "left", 5}, {$string_utils:from_value($math_utils:trunc(player.anx)), "left", 3}, {"/", "", 1}, {$string_utils:from_value(player.anx_max), "", 4}, {("[" + anx_rank) + "]", "left", 13}, {"Played: " + $string_utils:from_seconds(connected_seconds(this)), "right", 42}}; player:tell($string_utils:flex_col_format(row3)); player:tell($string_utils:space(col_width, "-")); player:tell($string_utils:format_two_col(((((("STR: " + $string_utils:from_value(player.str)) + "/") + $string_utils:from_value(player.str_max)) + " [+") + $string_utils:from_value(player.str_bon)) + "]", "EXP: " + $string_utils:group_number(player.exp), col_width)); player:tell($string_utils:format_two_col(((((("INT: " + $string_utils:from_value(player.int)) + "/") + $string_utils:from_value(player.int_max)) + " [+") + $string_utils:from_value(player.int_bon)) + "]", ((((("Level: " + $string_utils:from_value(player.lev)) + " out of ") + $string_utils:from_value(player:get_level_max())) + " [") + player.status) + "]", col_width)); player:tell($string_utils:format_two_col(((((("WIS: " + $string_utils:from_value(player.wis)) + "/") + $string_utils:from_value(player.wis_max)) + " [+") + $string_utils:from_value(player.wis_bon)) + "]", "You feel: " + player:get_health_description(), col_width)); player:tell($string_utils:format_two_col(((((("DEX: " + $string_utils:from_value(player.dex)) + "/") + $string_utils:from_value(player.dex_max)) + " [+") + $string_utils:from_value(player.dex_bon)) + "]", (((("Hitpoints: " + $string_utils:from_value(player.hp)) + "/") + $string_utils:from_value(player.hp_max)) + " -- wimpout at ") + $string_utils:from_value(player.wimpout),col_width)); player:tell($string_utils:format_two_col(((((("CHR: " + $string_utils:from_value(player.chr)) + "/") + $string_utils:from_value(player.chr_max)) + " [+") + $string_utils:from_value(player.chr_bon)) + "]", ((("Objects: " + $string_utils:group_number(quota_used)) + " bytes used of ") + $string_utils:group_number(quota_total)) + " max", col_width)); player:tell($string_utils:space(col_width, "-")); player:tell($string_utils:format_two_col("Combat wins: " + $string_utils:from_value(player.combat_wins), "Combat losses: " + $string_utils:from_value(player.combat_losses), col_width)); if ($object_utils:isa(player, $guest)) player:tell("Sorry ", this.name, ", but you won't see much change in these values until you become a registered agent..."); return; endif .
The following verb basically does the same thing as the stats verb described above, but for the entire agent collective. Thus, the beginning of the code is gathering stats on all currently connected agents. It also shows how many connections are being made with the phat-client. To see an example of what it looks like in use, check out the player stats screen in the combat system overview.
-------------------------------------------------------------------- $player:co*llective none none none permissions: rd col_width = 68; player_total = length(connected_players()); coll_ali = 0.0; coll_amb = 0.0; coll_anx = 0.0; coll_str = 0; coll_int = 0; coll_wis = 0; coll_dex = 0; coll_chr = 0; coll_combat_wins = 0; coll_combat_losses = 0; coll_exp = 0; coll_lev = 0; coll_hp = 0; coll_hp_max = 0; coll_wimpout = 0; phat_agents = 0; for p in (connected_players()) if (p.phat_client != 0) phat_agents = phat_agents + 1; endif coll_ali = coll_ali + p.ali; coll_amb = coll_amb + p.amb; coll_anx = coll_anx + p.anx; coll_str = coll_str + p.str; coll_int = coll_int + p.int; coll_wis = coll_wis + p.wis; coll_dex = coll_dex + p.dex; coll_chr = coll_chr + p.chr; coll_combat_wins = coll_combat_wins + p.combat_wins; coll_combat_losses = coll_combat_losses + p.combat_losses; coll_exp = coll_exp + p.exp; coll_lev = coll_lev + p.lev; coll_hp = coll_hp + p.hp; coll_hp_max = coll_hp_max + p.hp_max; coll_wimpout = coll_wimpout + p.wimpout; endfor coll_ali = coll_ali / tofloat(length(connected_players())); coll_amb = coll_amb / tofloat(length(connected_players())); coll_anx = coll_anx / tofloat(length(connected_players())); coll_str = coll_str / length(connected_players()); coll_int = coll_int / length(connected_players()); coll_dex = coll_wis / length(connected_players()); coll_wis = coll_dex / length(connected_players()); coll_chr = coll_chr / length(connected_players()); coll_exp = coll_exp / length(connected_players()); coll_lev = coll_lev / length(connected_players()); coll_hp = coll_hp / length(connected_players()); coll_hp_max = coll_hp_max / length(connected_players()); coll_wimpout = coll_wimpout / length(connected_players()); coll_combat_wins = coll_combat_wins / length(connected_players()); coll_combat_losses = coll_combat_losses / length(connected_players()); if (coll_ali <= 0.4) ali_rank = "very low"; elseif ((coll_ali >= 0.5) && (coll_ali < 1.5)) ali_rank = "low"; elseif ((coll_ali >= 1.5) && (coll_ali < 2.5)) ali_rank = "moderate"; elseif ((coll_ali >= 2.5) && (coll_ali < 3.5)) ali_rank = "high"; elseif (coll_ali >= 3.5) ali_rank = "very high"; endif if (coll_amb <= 0.4) amb_rank = "very low"; elseif ((coll_amb >= 0.5) && (coll_amb < 1.5)) amb_rank = "low"; elseif ((coll_amb >= 1.5) && (coll_amb < 2.5)) amb_rank = "moderate"; elseif ((coll_amb >= 2.5) && (coll_amb < 3.5)) amb_rank = "high"; elseif (coll_amb >= 3.5) amb_rank = "very high"; endif if (coll_anx <= 0.4) anx_rank = "very low"; elseif ((coll_anx >= 0.5) && (coll_anx < 1.5)) anx_rank = "low"; elseif ((coll_anx >= 1.5) && (coll_anx < 2.5)) anx_rank = "moderate"; elseif ((coll_anx >= 2.5) && (coll_anx < 3.5)) anx_rank = "high"; elseif (coll_anx >= 3.5) anx_rank = "very high"; endif player:tell(""); player:tell($string_utils:format_two_col("Stats for the agent collective:", (((("all [n = " + $string_utils:from_value(player_total)) + "]; ") + "phat-client [n = ") + $string_utils:from_value(phat_agents)) + "]", col_width)); player:tell($string_utils:space(col_width, "-")); row1 = {{"ALI: ", "left", 5}, {$string_utils:from_value($math_utils:trunc(coll_ali)), "left", 3}, {"/", "", 1}, {$string_utils:from_value(player.ali_max), "", 4}, {("[" + ali_rank) + "]*", "left", 13}, {"Time: " + ctime(), "right", 42}}; player:tell($string_utils:flex_col_format(row1)); row2 = {{"AMB: ", "left", 5}, {$string_utils:from_value($math_utils:trunc(coll_amb)), "left", 3}, {"/", "", 1}, {$string_utils:from_value(player.amb_max), "", 4}, {("[" + amb_rank) + "]*", "left", 13}}; player:tell($string_utils:flex_col_format(row2)); row3 = {{"ANX: ", "left", 5}, {$string_utils:from_value($math_utils:trunc(coll_anx)), "left", 3}, {"/", "", 1}, {$string_utils:from_value(player.anx_max), "", 4}, {("[" + anx_rank) + "]*", "left", 13}, {"*All attribute scores are averaged", "right", 42}}; player:tell($string_utils:flex_col_format(row3)); player:tell($string_utils:space(col_width, "-")); player:tell($string_utils:format_two_col(((("STR: " + $string_utils:from_value(coll_str)) + "/") + $string_utils:from_value(player.str_max)) + " [averaged]", ("EXP: " + $string_utils:group_number(coll_exp)) + " [averaged]", col_width)); player:tell($string_utils:format_two_col(((("INT: " + $string_utils:from_value(coll_int)) + "/") + $string_utils:from_value(player.int_max)) + " [averaged]", ((("Level: " + $string_utils:from_value(coll_lev)) + " out of ") + $string_utils:from_value(player:get_level_max())) + " [averaged]", col_width)); player:tell($string_utils:format_two_col(((("WIS: " + $string_utils:from_value(coll_wis)) + "/") + $string_utils:from_value(player.wis_max)) + " [averaged]", "", col_width)); player:tell($string_utils:format_two_col(((("DEX: " + $string_utils:from_value(coll_dex)) + "/") + $string_utils:from_value(player.dex_max)) + " [averaged]", "", col_width)); player:tell($string_utils:format_two_col(((("CHR: " + $string_utils:from_value(coll_chr)) + "/") + $string_utils:from_value(player.chr_max)) + " [averaged]", ((((("Hitpoints: " + $string_utils:from_value(coll_hp)) + "/") + $string_utils:from_value(coll_hp_max)) + " -- wimpout at ") + $string_utils:from_value(coll_wimpout)) + " [averaged]", col_width)); player:tell($string_utils:space(col_width, "-")); player:tell($string_utils:format_two_col(("Combat wins: " + $string_utils:from_value(coll_combat_wins)) + " [averaged]", ("Combat losses: " + $string_utils:from_value(coll_combat_losses)) + " [averaged]", col_width)); .
The consider verb shows you a brief stats summary of the player or monster object you are contemplating engaging in combat. It also tells you how likely you will be to win. To see an example of what it looks like in use, check out the player consider screen in the combat system overview.
-------------------------------------------------------------------- $player:con*sider any none none permissions: rd if ((!$object_utils:isa(dobj, $player)) && (!$object_utils:isa(dobj, $generic_monster))) player:tell("Not possible..."); return 0; elseif ($object_utils:isa(dobj, $player) && (dobj.stats_done != 1)) player:tell("It's not worth considering at this point..."); return 0; elseif ($object_utils:isa(dobj, $player) || $object_utils:isa(dobj, $generic_monster)) col_width = 68; if (player.name == dobj.name) player:tell("Rather egocentric of you..."); return 0; endif if ($object_utils:isa(dobj, $player)) obj_checker = dobj; elseif ($object_utils:isa(dobj, $generic_monster)) obj_checker = player; endif player:tell(""); player:tell($string_utils:space(col_width, "-")); player:tell($string_utils:format_two_col(("Stats for " + dobj.name) + ":", "EXP: " + $string_utils:group_number(dobj.exp), col_width)); player:tell($string_utils:format_two_col("Wins: " + $string_utils:from_value(dobj.combat_wins), ((((("Level: " + $string_utils:from_value(dobj.lev)) + " out of ") + $string_utils:from_value(length(dobj.exp_table) + 1)) + " [") + dobj.status) + "]", col_width)); player:tell($string_utils:format_two_col("Losses: " + $string_utils:from_value(dobj.combat_losses), (((("Hitpoints: " + $string_utils:from_value(dobj.hp)) + "/") + $string_utils:from_value(dobj.hp_max)) + " -- wimpout at ") + $string_utils:from_value(dobj.wimpout), col_width)); player:tell($string_utils:space(col_width, "-")); player:tell($string_utils:format_two_col(("Stats for " + player.name) + ":", "EXP: " + $string_utils:group_number(player.exp), col_width)); player:tell($string_utils:format_two_col("Wins: " + $string_utils:from_value(player.combat_wins), ((((("Level: " + $string_utils:from_value(player.lev)) + " out of ") + $string_utils:from_value(length(player.exp_table) + 1)) + " [") + player.status) + "]", col_width)); player:tell($string_utils:format_two_col("Losses: " + $string_utils:from_value(player.combat_losses), (((("Hitpoints: " + $string_utils:from_value(player.hp)) + "/") + $string_utils:from_value(player.hp_max)) + " -- wimpout at ") + $string_utils:from_value(player.wimpout), col_width)); player:tell($string_utils:space(col_width, "-")); if ($object_utils:isa(player, $guest)) player:tell("Be aware ", this.name, ", until you properly register you won't be able to effectively engage in battle..."); return; elseif ($object_utils:isa(dobj, $player) && (player.hp_max > (dobj.hp_max + 10))) player:tell("You could probably take ", dobj.name, "..."); elseif ($object_utils:isa(dobj, $player) && (player.hp_max < (dobj.hp_max - 10))) player:tell("You better watch out, ", dobj.name, " seems pretty tough..."); elseif ($object_utils:isa(dobj, $player)) player:tell("Looks like you and ", dobj.name, " are pretty evenly matched..."); elseif ($object_utils:isa(dobj, $generic_monster)) player:tell("Be careful, things are not always as they seem..."); endif endif .
The generate stats verb is responsible for setting your scores for strength, intelligence, wisdom, dexterity, and charisma. The scores are determined when you roll five nine-sided dice in a special location, where you can also train in the various tactics and tongues needed for combat.
-------------------------------------------------------------------- $player:gen_stats this none this permissions: rxd done = player.stats_done; max_limit = 35; while (done == 0) rolls = 0; subtotal = 0; stats = {}; while (rolls < 5) current_roll = random(9); rolls = rolls + 1; subtotal = subtotal + current_roll; if (subtotal > max_limit) stats = {}; rolls = 0; subtotal = 0; else stats = listappend(stats, current_roll); endif endwhile player.str = stats[1]; player.int = stats[2]; player.wis = stats[3]; player.dex = stats[4]; player.chr = stats[5]; col_width=68; player:tell(""); player:tell("Randomly generated attribute scores for ", player.name, ":"); player:tell($string_utils:space(col_width, "-")); player:tell($string_utils:format_two_col(" STRENGTH: " + $string_utils:from_value(player.str), " out of a maximum possible score of " + $string_utils:from_value(player.str_max) + " ",col_width)); player:tell($string_utils:format_two_col(" INTELLIGENCE: " + $string_utils:from_value(player.int), " out of a maximum possible score of " + $string_utils:from_value(player.int_max) + " ",col_width)); player:tell($string_utils:format_two_col(" WISDOM: " + $string_utils:from_value(player.wis), " out of a maximum possible score of " + $string_utils:from_value(player.wis_max) + " ",col_width)); player:tell($string_utils:format_two_col(" DEXTERITY: " + $string_utils:from_value(player.dex), " out of a maximum possible score of " + $string_utils:from_value(player.dex_max) + " ",col_width)); player:tell($string_utils:format_two_col(" CHARISMA: " + $string_utils:from_value(player.chr), " out of a maximum possible score of " + $string_utils:from_value(player.chr_max) + " ",col_width)); player:tell($string_utils:space(col_width, "-")); roll_again = $command_utils:yes_or_no("Regenerate scores?"); if (!roll_again) done = 1; endif endwhile player.stats_done = 1; player:tell("You have successfuly selected your scores!"); .
After you have successfully generated your scores, you are requested to input information that gets displayed when other agents look at or finger you. This includes such things as gender, personal description, research interests, and associated home page URL. This verb is essentially a subset of a verb that ships with v1.1 the High Wired enCore implementation of LambdaMOO.
-------------------------------------------------------------------- $player:reqprefs this none none permissions: rxd set_task_perms(player); if ($object_utils:isa(player, $guest)) return 0; endif if (player.stats_done !=1) $player:gen_stats(); endif if (player.reqprefs != 1) finished = 0; while (!finished) player:tell("Now please respond to the following before continuing..."); "------------------------- Gender ------------------------"; player:tell(""); player:tell("Setting Your Gender"); player:tell("-------------------"); player:tell("Your gender is currently set to ", player.gender, "."); player:tell("Available genders: ", $string_utils:english_list($gender_utils.genders, "", " or ")); if (gender = $command_utils:read("your choice. Press RETURN or ENTER to continue")) if (gender in $gender_utils.genders) $gender_utils:set(player, gender); player.gender = gender; player:tell("Your gender has been set to ", gender, "."); else player:tell("Sorry, ", gender, " is not an available gender. Please try again."); player:reqprefs(); return 0; endif endif player:tell(""); finished = 1; endwhile finished = 0; while (!finished) "------------------------- Description -------------"; player:tell("Describing Yourself"); player:tell("-------------------"); if (player.description) player:tell("Your description is currently set to:"); player:tell_lines(player.description); player:tell(""); endif if (descr = $command_utils:read("a description of yourself. Press RETURN or ENTER to continue")) player:tell(""); player:tell("You typed:"); player:tell(descr); if ($command_utils:yes_or_no("Are you sure you want to change your description to this?")) player.description = descr; player:tell("Description set."); else player:tell("No changes."); endif endif player:tell(""); finished = 1; endwhile finished = 0; while (!finished) "------------------------- Research/Interests -------------"; player:tell("Research and Interests"); player:tell("----------------------"); if (player.interests_msg) player:tell("Your research and interests message is currently set to:"); player:tell_lines(player.interests_msg); player:tell(""); endif if (msg = $command_utils:read("your research and other interests. Press RETURN or ENTER to continue")) player:tell(""); player:tell("You typed:"); player:tell(msg); if ($command_utils:yes_or_no("Are you sure you want to change your research and interests message to this?")) player.interests_msg = msg; player:tell("Research and Interest Message Set."); else player:tell("No changes."); endif endif finished = 1; player:tell(""); endwhile finished = 0; while (!finished) "------------------------- Home Page ----------------------"; player:tell("Home Page"); player:tell("---------"); if (player.homepage) player:tell("Your home page is currently: ", player.homepage); player:tell(""); endif if (homepage = $command_utils:read("the URL for your World Wide Web home page. Press RETURN or ENTER to continue")) player:tell(""); player:tell("You typed:"); player:tell(homepage); if ($command_utils:yes_or_no("Are you sure you want to set your home page to this?")) player.homepage = homepage; player.url_address = homepage; player:tell("Homepage set."); else player:tell("No changes."); endif endif finished = 1; endwhile player.reqprefs = 1; player:tell(""); player:tell("Finished. Exiting..."); else player:tell("The requested preferences have already been set!"); endif .
The next series of verbs are all related to combat. They should be pretty straight forward. We start with a tactic verb. There are six tactics that get mobilized in relation to nine tongues (see the skills screen in the combat system overview). Each of the tactics works pretty much the same way, so we'll only give a single example.
First is a check to see if the caller is a $generic_monster, or a $player. Then we see if the caller is skilled (practiced) in the tactic being used. We also check to see if the thing being attacked understands the tongue the attacker is speaking in. If not, nothing happens. Then we check the funds adjustment based on combat location, chosen tactic, and tongue. Finally, we do the damage calculations, and determine waht sort of notifications to send to the attacker, the attacked, and the observers.
-------------------------------------------------------------------- $player:bullshit this in/inside/into any permissions: rd if ($object_utils:isa(dobj, $generic_monster)) dobj:bs( dobj, prepstr, iobj ); elseif ($object_utils:isa(dobj, $player)) if (player.bullshit == 0) player:tell("You're not skilled in that tactic!"); return; endif if (dobj.bullshit == 0) player:tell(dobj.name, " is not skilled in that tactic!"); return; endif tongues = {"geek", "ghetto", "imperialist", "jingoist", "marxist", "pomo"}; tongue_to_use = iobjstr; known_tongues = {}; for tongue in (tongues) if (player.(tongue) > 0 && dobj.(tongue) > 0) known_tongues = listappend(known_tongues, tongue); endif endfor if (!(tongue_to_use in known_tongues)) player:tell("Luckily for ", dobj.name, ", ", dobj.ps, " doesn't understand you."); return; endif if (player.name == dobj.name) player:tell("You lay it on yourself pretty heavy..."); return; endif "check for funds changes depending on space, tactic and tongue"; total_funds_change = player:calc_cost(player.game_space, "bullshit", tongue); damage = random(5); if (damage == 1) damage_str = "hitpoint"; else damage_str = "hitpoints"; endif dobj:tell(player.name, " is trying to bullshit you in ", tongue_to_use, "!"); player_attack_successful = random(100) <= ((15 + player.int) + player.(tongue_to_use)); defender_attack_successful = random(100) <= ((15 + dobj.int) + dobj.(tongue_to_use)); if (player.hp <= player.wimpout) player:tell("You can't do that! You're too much of a wimp..."); return; elseif (dobj.hp <= dobj.wimpout) player:tell("You can't do that! ", dobj.name, " is too much of a wimp..."); return; endif if (player.practice_sessions_attended == 0 || dobj.practice_sessions_attended == 0) player:tell("There's a serious language gap!"); return; endif if (player_attack_successful && (!defender_attack_successful)) player:tell("You just bullshit ", dobj.name, " for ", damage, " ", damage_str, "!"); dobj:tell("You got bullshit by ", player.name, " and couldn't help but like it!"); dobj:tell("It cost you ", damage, " ", damage_str, "!"); dobj:hp_modify( -damage, this ); player:adjust_funds( total_funds_change ); fork( 2 ) player:tell( "Whoa! Your funds just got adjusted..." ); endfork elseif (defender_attack_successful && (!player_attack_successful)) player:tell("Your attempt backfired! You got nailed by ", dobj.name, " in ", tongue_to_use, "!"); player:tell("You lost ", damage, " ", damage_str, "!"); dobj:tell("Reversal! You counter-attacked ", player.name, " back in ", tongue_to_use, "..."); dobj:tell("It cost ", player.name, " ", damage, " ", damage_str, "!"); player.location:announce_all_but({dobj, player}, dobj.name, " just nailed ", player.name, "!"); player:hp_modify( -damage, dobj ); elseif (player_attack_successful && defender_attack_successful) player:tell("You bullshit ", dobj.name, " in ", tongue_to_use, " for ", damage, " ", damage_str, "!"); player:tell("But you also got hit for ", damage, " ", damage_str, "!"); dobj:tell(player.name, " just bullshit you for ", damage, " ", damage_str, ", but it felt like a favor!"); dobj:tell("But you got ", player.name, " back for ", damage, " ", damage_str, "!"); player.location:announce_all_but({dobj, player}, player.name, " and ", dobj.name, " are doing each other some serious damage, but they sure seem to be enjoying it!!!"); player:hp_modify( -damage, dobj ); dobj:hp_modify( -damage, player ); else player:tell("You attempt to bullshit ", dobj.name, " for quite some time in ", tongue_to_use, ". You feel rather weary..."); player.location:announce_all_but({dobj, player}, player.name, " is trying to bullshit ", dobj.name, " in ", tongue_to_use, "!"); endif endif .
Next comes a cost calculator (calc_cost) that determines whether funds need adjusting during battle. In this case, funds adjustment is based on combat location, chosen tactic and tongue. The cost calculator verb is followed by a hitpoint check (hp_check) which looks to see whether a wimpout factor has been reached. If so, it increments the combat losses property, sends an XML message to MAM, and teleports the player home. The following hitpoint modifier verb (hp_modify) is what calls the hitpoint check.
-------------------------------------------------------------------- $player:calc_cost this none this permissions: rxd {space, tactic, tongue} = args; " Check for funds changes depending on space, tactic and tongue. "; fund_transitions = { { "bullshit", { { "cp", "pomo", 1.0 }, { "cp", "marxist", 1.0 }, { "cp", "ghetto", 1.0 }, { "cp", "geek", -1.0 }, { "cp", "imperialist", -1.0 }, { "cp", "jingoist", -1.0 }, { "fs", "pomo", 1.0 }, { "fs", "marxist", 1.0 }, { "fs", "geek", 1.0 }, { "fs", "ghetto", -1.0 }, { "fs", "jingoist", -1.0 }, { "fs", "imperialist", -1.0 }, { "si", "geek", 1.0 }, { "si", "imperialist", 1.0 }, { "si", "jingoist", 1.0 }, { "si", "marxist", -1.0 }, { "si", "pomo", -1.0 } } }, { "compliment", { { "cp", "pomo", 1.0 }, { "cp", "marxist", 1.0 }, { "cp", "ghetto", 1.0 }, { "fs", "pomo", 1.0 }, { "fs", "marxist", 1.0 }, { "fs", "geek", 1.0 } } }, { "confuse", { { "cp", "pomo", 1.0 }, { "cp", "marxist", 1.0 }, { "cp", "ghetto", 1.0 }, { "cp", "geek", -1.0 }, { "cp", "imperialist", -1.0 }, { "cp", "jingoist", -1.0 }, { "fs", "pomo", 1.0 }, { "fs", "marxist", 1.0 }, { "fs", "geek", 1.0 }, { "fs", "ghetto", -1.0 }, { "fs", "jingoist", -1.0 }, { "fs", "imperialist", -1.0 }, { "si", "geek", 1.0 }, { "si", "imperialist", 1.0 }, { "si", "jingoist", 1.0 }, { "si", "marxist", -1.0 }, { "si", "pomo", -1.0 } } }, { "deceive", { { "cp", "geek", -3.0 }, { "cp", "imperialist", -3.0 }, { "cp", "jingoist", -3.0 }, { "fs", "pomo", 2.0 }, { "fs", "marxist", 2.0 }, { "fs", "geek", 2.0 }, { "si", "geek", 3.0 }, { "si", "imperialist", 3.0 }, { "si", "jingoist", 3.0 } } }, { "insult", { { "cp", "geek", -3.5 }, { "cp", "imperialist", -3.5 }, { "cp", "jingoist", -3.5 }, { "fs", "ghetto", -4.0 }, { "fs", "jingoist", -4.0 }, { "fs", "imperialist", -4.0 }, { "si", "marxist", -7.0 }, { "si", "pomo", -7.0 } } }, { "pulverize", { { "cp", "geek", -9.0 }, { "cp", "imperialist", -9.0 }, { "cp", "jingoist", -9.0 }, { "fs", "ghetto", -9.0 }, { "fs", "jingoist", -9.0 }, { "fs", "imperialist", -9.0 }, { "si", "marxist", -10.0 }, { "si", "pomo", -10.0 } } }, { "indoctrinate", { { "fs", "pomo", 10.0 }, { "fs", "marxist", 10.0 }, { "fs", "geek", 10.0 }, { "fs", "ghetto", -10.0 }, { "fs", "jingoist", -10.0 }, { "fs", "imperialist", -10.0 }, { "si", "geek", 10.0 }, { "si", "imperialist", 10.0 }, { "si", "jingoist", 10.0 }, { "si", "marxist", -10.0 }, { "si", "pomo", -10.0 } } } }; total_funds_change = 0.0; for tactic_transition_set in (fund_transitions) {transition_tactic, transition_set} = tactic_transition_set; if( transition_tactic == tactic ) for transition_mapping in (transition_set) {transition_space, transition_tongue, funds_change} = transition_mapping; if( space == transition_space && tongue == transition_tongue ) total_funds_change = total_funds_change + funds_change; endif endfor endif endfor return total_funds_change; .
-------------------------------------------------------------------- $player:hp_check this none this permissions: rxd opponent = args[1]; if (this.hp <= this.wimpout && this.wimpout > 0) this:tell("You wimped out and ran away!"); this.location:announce_all_but({opponent, this}, this.name, " just wimped out and ran away!"); this.hp = this.wimpout; this.combat_losses = this.combat_losses + 1; if (this.phat_client && (this.wimpout_message != "")) for line in (this.wimpout_message) this:tell(line); endfor endif move(this, this.home); return; endif if (this.hp <= 0) this:death(opponent); endif .
-------------------------------------------------------------------- $player:hp_modify this none this permissions: rxd hp_modifier = args[1]; opponent = args[2]; this.hp = this.hp + hp_modifier; this:hp_check(opponent); .
The wimpout verb is what allows the player to set a floor for the hitpoints. This means that when the hitpoints reach the floor, the player runs away from combat and can no longer suffer damaged.
-------------------------------------------------------------------- $player:wimp*out this at/to any permissions: rd if ($object_utils:isa(player, $guest)) player:tell("You can't set that property..."); return; endif if (!$object_utils:isa(player, this.owner)) player:tell("Don't you wish! You can only change your own wimpout factor!"); player:tell("No changes."); return 0; endif wimpout = $code_utils:tonum(iobjstr); if (wimpout == player.wimpout) player:tell("Your wimpout factor was already set to ", wimpout, "!"); player:tell("No changes."); elseif (wimpout > player.hp || wimpout > player.hp_max) player:tell("Your wimpout factor must be less than your remaining hitpoints!"); player:tell("No changes."); elseif (wimpout <= player.hp && wimpout > 0) player:tell("Your new wimpout score is '", wimpout, "', out of ", player.hp_max, " total hitpoints. You currently have ", player.hp, " hitpoints remaining."); player.wimpout = wimpout; elseif (wimpout == 0) player:tell("Your new wimpout score is '", wimpout, "', out of ", player.hp_max, " total hitpoints. You currently have ", player.hp, " hitpoints remaining. Watch out, you could get slaughtered!"); player.wimpout = wimpout; endif .
The next several verbs are responsible for doing level checks and adjustments. The first gets the current level, the next ('get_level_max') is called from within the stats screen when the player's current level is shown in relation to the maximum number of levels possible. The experience modification verb is called during experience point adjustment and looks to see if it's time to move the player to the next level. If so the player levels up, and the player's status is appropriately adjusted.
-------------------------------------------------------------------- $player:get_level this none this permissions: rxd current_level = 1; for exp_required in (player.exp_table) if(this.exp > exp_required) current_level = current_level + 1; endif endfor return current_level; .
-------------------------------------------------------------------- $player:get_level_max this none this permissions: rxd return length(player.exp_table) + 1; .
-------------------------------------------------------------------- $player:exp_modify this none this permissions: rxd exp_gained = args[1]; current_exp = this.exp; current_level = this.lev; this.exp = this.exp + exp_gained; if(this:get_level(this.exp) > this.lev) this:level_up(); endif .
-------------------------------------------------------------------- $player:level_up this none this permissions: rxd player:tell("You leveled! Congratulations..."); this.lev = this:get_level(this.exp); this.hp_max = this.hp_max + random(7) + 15; this.hp = this.hp_max; player:update_status(); .
-------------------------------------------------------------------- $player:update_status this none this permissions: rxd status_descriptions = player.player_status_cp; if(player.game_space == "fs") status_descriptions = player.player_status_fs; elseif(player.game_space == "si") status_descriptions = player.player_status_si; endif player.status = status_descriptions[player.lev]; .
The death verb is what gets executed when a player loses all hitpoints because a wimpout factor was not set. Basically everything is lost and the player has to start from scratch. All the data ingested into the agent via the phat-client is wiped out as well!
-------------------------------------------------------------------- $player:death this none this permissions: rxd " Passes XML msg and resets player when hp = 0. "; if ((dobj.phat_client != 0) && (dobj.death_message != "")) for line in (dobj.death_message) dobj:tell(line); endfor endif opponent = args[1]; dobj:tell("You got slaughtered!"); dobj.location:announce_all_but({opponent, dobj}, player.name, " just slaughtered ", dobj.name, "! Not a pretty sight..."); dobj.lev = 1; dobj.hp = 10; dobj.wimpout = 0; dobj.exp = 0; dobj.str = 0; dobj.str_bon = 0; dobj.int = 0; dobj.int_bon = 0; dobj.wis = 0; dobj.wis_bon = 0; dobj.dex = 0; dobj.dex_bon = 0; dobj.chr = 0; dobj.chr_bon = 0; dobj.bullshit = 0; dobj.compliment = 0; dobj.confuse = 0; dobj.debate = 0; dobj.deceive = 0; dobj.indoctrinate = 0; dobj.insult = 0; dobj.pacify = 0; dobj.pulverize = 0; dobj.geek = 0; dobj.ghetto = 0; dobj.imperialist = 0; dobj.jingoist = 0; dobj.marxist = 0; dobj.pomo = 0; dobj.practice_sessions_remaining = 3; dobj.practice_sessions_attended = 0; dobj.stats_done = 0; dobj.game_space = "null"; dobj.combat_wins = 0; dobj.combat_losses = 0; dobj.stats_done = 0; fork (2) dobj:tell("Your scores were all reset!"); endfork fork (3) dobj:tell("Looks like you're starting from scratch! Bummer..."); move(dobj, dobj.home); endfork .
The following verb code adjusts the health description in relation to the current hitpoints. It starts by checking to see if the player has already generated some stats (which in our case happens by rolling the dice). If stats have not been generated, the player is flagged "helpless" since we know that certain conditions have not yet been met to make combat possible.
-------------------------------------------------------------------- $player:get_health_description this none this permissions: rxd highest_multiplier = 0.0; for health_state in (this.health_states) if (player.stats_done !=1 || player.hp == 0) current_description = "helpless"; endif {multiplier, description} = health_state; if(multiplier > highest_multiplier) if(tofloat(this.hp) > tofloat(this.hp_max) * multiplier) highest_multiplier = multiplier; current_description = description; endif endif endfor if ($object_utils:isa(player, $guest)) current_description = "helpless"; return current_description; else return current_description; endif .
The next verb does funds adjustment when called from an object in-MOO or from a MAM process deemed worthy of increasing or decreasing player wealth. It checks to see if the player is legitimate and not just a guest. If so, it sends a MAM message, and then appropriately adjusts the player property.
-------------------------------------------------------------------- $player:adjust_funds this none this permissions: rxd " Pass a message to MAM for adjusting funds. "; " Can be applied to any MOO object. "; if ($object_utils:isa(player, $player) && !$object_utils:isa(player, $guest)) {funds_transfer} = args; adjust_funds_message = {"‹mam-message type=\"mam.messaging.DoAdjustFundsMessage\"›", "‹Source pref=\"true\" type=\"mam.internetworking.RoutingTag\" value=\"**myClient**\" /›", "‹Destination pref=\"true\" type=\"mam.internetworking.RoutingTag\" value=\"local.ipAgent\" /›", "‹FundsTransfer pref=\"true\" type=\"Double\" value=\"", $string_utils:from_value(funds_transfer), "\" /›", "‹/mam-message›"}; if (this.phat_client) for line in (adjust_funds_message) this:tell(line); endfor endif player.funds = player.funds + funds_transfer; endif .
Here we have the barter verb that provides the foundation for the in-MOO bartering system. It begins with a couple of checks to make sure that the initiator of a bartering session is a legitimate agent (which could include a special $bot type, detailed below), and not a guest. Then it sets some variables for handling how the agent you want to barter with displays the available objects for trading, and for verifying that those objects are indeed tradeable (a property flag defined on a special generic object we created called the '$tradeable_object', which has a few other unique properties listed below, and no special verbs). Then there is a bunch of stuff allowing the back-n-forth between agents, and eventual transfer of the object and adjustment of funds if a price is agreed upon.
-------------------------------------------------------------------- $player:barter any none none permissions: rxd if (parent(dobj) == $barter_bot) dobj:list(); return 0; endif if ($object_utils:isa(player, $guest)) player:tell("You can't barter until you properly register..."); return 0; endif if (!$object_utils:isa(dobj, $player)) player:tell("Impossible! You need to barter with a legitimate agent..."); return 0; endif if (dobj == this) player:tell("You barter with yourself for a while."); fork (3) player:tell("You start to look rather foolish..."); endfork return 0; endif my_objects = {}; for available_object in (player:contents()) if ($object_utils:isa(available_object, $tradeable_object)) my_objects = listappend(my_objects, available_object); endif endfor others_objects = {}; for available_object in (dobj:contents()) if ($object_utils:isa(available_object, $tradeable_object)) others_objects = listappend(others_objects, available_object); endif endfor " TODO: abort if no tradeable objects "; " Check to see if dobj has outstanding bids. "; outstanding_bids = 0; for available_object in (my_objects) if ($object_utils:isa(available_object, $tradeable_object)) if ((available_object.last_bidder == dobj) && (available_object.currently_bid_on == 1)) player:tell("There is an outstanding bid of ", $string_utils:format_funds(available_object.last_offer), " for ", available_object.aliases[1], "."); player:tell("Would you like to accept this offer?"); answer = $command_utils:read("y/n"); if (answer == "y") player:tell("Accepted..."); " Validate that funds exist. "; if (dobj.funds >= available_object.last_offer) player:tell("All sales are final!"); dobj:tell("You just acquired a brand new ", available_object.aliases[1], "."); player:adjust_funds(available_object.last_offer); dobj:adjust_funds(0.0 - available_object.last_offer); available_object.last_offer = 0.0; " Transfer ownership. "; $wiz_utils:set_owner(available_object, dobj); move(available_object, dobj); else player:tell("The transaction failed! ", dobj.name, " did not have the needed funds..."); dobj:tell("You don't have enough money to pay ", player.name, "."); endif else player:tell("Rejected!"); dobj:tell("Your offer of ", $string_utils:format_funds(available_object.last_offer), " for ", available_object.aliases[1], " has been rejected."); available_object.currently_bid_on = 0; endif outstanding_bids = 1; endif endif endfor if (outstanding_bids == 1) return 0; endif " Let the player browse through the other player's stuff. "; if (length(others_objects) > 0) player:tell_contents(others_objects); else player:tell(dobj.name, " has nothing for you to acquire."); return 0; endif finished = 0; while (!finished) player:tell("What would you like to acquire from ", dobj.name, "?"); if (desired_str = $command_utils:read("item name")) " Check to see that desired is contained in player objects. "; desired_found = 0; for available_object in (others_objects) for available_object_alias in (available_object.aliases) if (available_object_alias == desired_str) desired = available_object; desired_found = 1; endif endfor endfor if (desired_found == 0) player:tell("Unfortunately, ", desired_str, " is not available."); else " Check to see that desired is a tradeable object. "; if ($object_utils:isa(desired, $tradeable_object)) player:tell("You have selected \"", desired_str, "\"."); goods = {}; for m in (caller.contents) if ($object_utils:isa(m, $tradeable_object)) goods = listappend(goods, m.name); if (desired_str == m.name) caller:tell("Unfortunately, you can't acquire another ", desired_str, "!"); return; endif endif endfor finished = 1; else player:tell("Unfortunately, ", desired_str, " is not a tradeable object."); endif endif endif endwhile finished = 0; while (!finished) player:tell("How much are you offering?"); if (offer_str = $command_utils:read("amount")) try offer = tofloat(offer_str); if (offer > player.funds) player:tell("You can't afford ", $string_utils:format_funds(offer), "! You only have ", $string_utils:format_funds(player.funds), " remaining..."); return; endif player:tell("You offered: ", $string_utils:format_funds(offer), "..."); dobj:tell(player.name, " has just offered you ", $string_utils:format_funds(offer), " for your \"", desired_str, "\"."); desired.last_offer = offer; desired.last_bidder = player; desired.currently_bid_on = 1; finished = 1; except (E_INVARG) player:tell("Huh? Try again..."); endtry endif endwhile . examples of $tradeable_object properties: $tradeable_object.last_offer [float so we get dollars and cents] $tradeable_object.last_bidder [object number of last bidder] $tradeable_object.currently_bid_on [boolean set during barter] $tradeable_object.msrp [float referencing manufacturer's suggested retail price]
PLAYER PROPERTIES
The property values are fairly self explanatory. Comments about what the property does and its value are set off by brackets. It is assumed you will be able to get the basic idea and extend as needed. Properties get set in several ways: 1) when called from within other verbs (most common); 2) when called from MAM (next most common); or 3) when modified by the player through some sort of interface. The trickiest part is setting the permission bits properly so that players can't change things they aren't supposed to with the @set command, while at the same time allowing other verbs to change things internal to MOO, or more importantly for our purposes, allowing properties to be changed from MAM. We have a stealthy way of doing this through calls passed from MAM to a special object hidden in MOO.
$player.phat_client [whether connected via phat_client - boolean] $player.language [language being spoken - "string"] $player.chip [whether id chip is present or not - boolean] $player.stats_done [whether stats generated or not - boolean] $player.reqprefs [whether summary info filled out or not - boolean] $player.ali [alienation - float] $player.ali_max [alienation max - float] $player.amb [ambition - float] $player.amb_max [ambition max - float] $player.anx [anxiety - float] $player.anx_max [anxiety max - float] $player.str [strength - integer] $player.str_max [strength max - integer] $player.int [intelligence - integer] $player.int_max [intelligence max - integer] $player.wis [wisdom - integer] $player.wis_max [wisdom max - integer] $player.dex [dexterity - integer] $player.dex_max [dexterity max - integer] $player.chr [charisma - integer] $player.chr_max [charisma max - integer] $player.str_bon [strength bonus - integer] $player.int_bon [intelligence bonus - integer] $player.wis_bon [wisdom bonus - integer] $player.dex_bon [dexterity bonus - integer] $player.chr_bon [charisma bonus - integer] $player.funds [amount of available money - integer] $player.exp [experience points - integer] $player.exp_table [experience point breakdown for levels - {"list"}] $player.lev [level based on experience points - integer] $player.status [ranking based on level - "string"] $player.health_states [feeling states based on hitpoints - {"list"}] $player.hp [currently available hitpoints - integer] $player.hp_max [maximum hitpoints - integer] $player.wimpout [hitpoint value determining when to run from combat - integer] $player.wimpout_message [XML message passed to MAM on wimpout - {"string"}] $player.death_message [XML message passed to MAM on death - {"string"}] $player.combat_wins [battles won - integer] $player.combat_losses [battles lost - integer] $player.practice_sessions_attended [cumulative tactic or tongue practices - integer] $player.practice_sessions_remaining [remaining tactic or tongue practices - integer] $player.(tactic) [proficiency in combat tactic - percent] $player.(tongue) [proficiency in combat tongue - percent] $player.game_space [last space/$room adventured in - "string"] $player.player_status_cp [status list used in specific quest space - {"list"}] $player.player_status_fs [status list used in specific quest space - {"list"}] $player.player_status_si [status list used in specific quest space - {"list"}] examples of list properties: $player.exp_table - {1500, 12000, 40500, etc.} $player.health_states - {{0.000001,"panicked"},{0.2,"depressed"},{0.3,"edgy"},{"etc."}} $player.player_status_cp/fs/si = {"initiate","convert","adherent","etc."}
MONSTER VERBS AND PROPERTIES
We also wanted to have "monster" objects that were NPCs, and would be confronted as obstacles during player quests. To do this we created a $generic_monster object (obj #155 in this case) that would be used as a parent for spawning new monsters for local customization if need be.
NOTE: To add an object to the generic objects database, you simply have to: 1) add the obj# to the #0.class_registry; and then 2) @prop #0.obj_name to #0.
MONSTER VERBS
The first verb responsible for tactics is quite simple, it does a reflection from the tactic executed by the player. The next several do hit point checks and modification as well as status adjustment.
-------------------------------------------------------------------- $generic_monster:tactic this in/inside/into any permissions: rd this:provoked_attack(iobjstr, 1, "tactic"); .
-------------------------------------------------------------------- $generic_monster:hp_check this none this permissions: rxd opponent = args[1]; if (this.hp <= this.wimpout && this.wimpout > 0) this:tell("You wimped out and ran away!"); this.location:announce_all_but({opponent, this}, this.name, " just wimped out and ran away!"); this.hp = this.wimpout; this.combat_losses = this.combat_losses + 1; if (this.phat_client && (this.wimpout_message != "")) for line in (this.wimpout_message) this:tell(line); endfor endif move(this, this.home); return; endif if (this.hp <= 0) this:death(opponent); endif .
-------------------------------------------------------------------- $generic_monster:hp_modify this none this permissions: rxd hp_modifier = args[1]; opponent = args[2]; this.hp = this.hp + hp_modifier; this:hp_check(opponent); .
-------------------------------------------------------------------- $generic_monster:update_status this none this permissions: rxd status_descriptions = this.player_status_cp; if(this.game_space == "fs") status_descriptions = this.player_status_fs; elseif(this.game_space == "si") status_descriptions = this.player_status_si; endif this.status = status_descriptions[this.lev]; .
If the player is victorious in combat, extra practice sessions are granted for improving tactics and tongues, the monster goes "home", and hitpoint adjustments get made.
-------------------------------------------------------------------- $generic_monster:death this none this permissions: rxd player.location:announce(player.name, " just trashed ", this.name, "! Not a pretty sight..."); player.combat_wins = player.combat_wins + 1; practice_sessions_granted = random(2); if (practice_sessions_granted == 1) psg = "session"; else psg = "sessions"; endif player.practice_sessions_remaining = player.practice_sessions_remaining + practice_sessions_granted; fork (2) player:tell(this.name, " disappears..."); player.location:announce(this.name, " disappears..."); player:tell("You just earned ", practice_sessions_granted, " practice ", psg, "! You now have ", player.practice_sessions_remaining, " remaining."); player:exp_modify(this.hp_max + random(player.str * 2)); move(this, this.home); endfork .
The final two verbs are responsible for handling the attacks. The first is a provoked attack, meaning it is in response to an attack initiated by the player, the second is an unprovoked attack, meaning it is initiated by the monster. Both verbs follow the same basic structure: they check if game spaces match, determine which tactics and tongues will be used, figure out how to apply the hitpoint damage, and spit out some strings for the benefit of telling combatants and observers what's going on.
-------------------------------------------------------------------- $generic_monster:provoked_attack this none this permissions: rxd player_obj = player; monster_obj = this; if(monster_obj.game_space != player_obj.game_space); player:tell("You can't seem to communicate with ", monster_obj.name, " no matter how hard you try!"); return; endif {tongue_to_use, player_initiated, player_tactic} = args; tactics = {"tactic1", "tactic2", "tactic3", "etc."}; tongues = {"tongue1", "tongue2", "tongue3", "etc."}; if(player_tactic == "") player_tactic=tactics[random(3)]; endif known_tactics = {}; for tactic in (tactics) if(player_obj.(tactic) > 0) known_tactics = listappend(known_tactics, tactic); endif endfor if (!(player_tactic in known_tactics) && player_initiated) player:tell("You're not skilled in that tactic!"); return; endif if(tongue_to_use == "") tongue_to_use = tongues[random(3)]; endif known_tongues = {}; for tongue in (tongues) if(player_obj.(tongue) > 0) known_tongues = listappend(known_tongues, tongue); endif endfor if (!(tongue_to_use in known_tongues) && player_initiated) player:tell(monster_obj.name, " doesn't understand you..."); return; endif if (player.hp <= player.wimpout) player:tell("You can't do that! You're too much of a wimp..."); return; endif total_funds_change = player:calc_cost(player.game_space, player_tactic, tongue_to_use); player_damage = random(5); if(player_damage == 1) player_damage_str = "hitpoint"; else player_damage_str = "hitpoints"; endif monster_damage = 1 + (random(10 + player_obj.(player_tactic)) / 10); if(monster_damage == 1) monster_damage_str = "hitpoint"; else monster_damage_str = "hitpoints"; endif player_attack_successful = random(100) <= ((15 + player_obj.prop) + player_obj.(tongue_to_use)); if(player_obj.(tongue_to_use) <= 0) player_attack_successful = 0; endif defender_attack_successful = random(100) <= ((20) + monster_obj.(tongue_to_use)); if (monster_obj.hp <= monster_obj.wimpout && monster_obj.hp > 0) player:tell("You can't do that! ", monster_obj.name, " is too much of a wimp..."); return; endif if (player.hp <= player.wimpout && player.hp > 0) return; endif if(monster_obj.hp < 1) return; endif if (player_attack_successful && (!defender_attack_successful)) player:tell("You just nailed ", monster_obj.name, " for ", monster_damage, " ", monster_damage_str, "!"); monster_obj:hp_modify(-monster_damage, this); player_obj:adjust_funds(total_funds_change); fork(2) player:tell("Whoa! Your funds just got adjusted..."); endfork elseif (defender_attack_successful && (!player_attack_successful)) player:tell("Your verbal seduction backfired! You got persuaded by ", monster_obj.name, " in ", tongue_to_use, "!"); player:tell("You lost ", player_damage, " ", player_damage_str, "!"); player.location:announce_all_but({monster_obj, player}, monster_obj.name, " made ", player.name, " see things from ", player.pp, " point of view!"); player:hp_modify(-player_damage, monster_obj); elseif (player_attack_successful && defender_attack_successful) player:tell("You sweet talked ", monster_obj.name, " in ", tongue_to_use, " for ", monster_damage, " ", monster_damage_str, "!"); player:tell("But you also got sweet talked back by ", monster_obj.name, " for ", player_damage, " ", player_damage_str, "!"); player.location:announce_all_but({monster_obj, player}, player.name, " and ", monster_obj.name, " are going on ad nauseum in ", tongue_to_use, "..."); player:hp_modify(-player_damage, monster_obj); monster_obj:hp_modify(-monster_damage, player); else player:tell("You struggle to speak with ", monster_obj.name, " for quite some time in ", tongue_to_use, ". You feel rather weary..."); player.location:announce_all_but({monster_obj, player}, player.name, " and ", monster_obj.name, " are struggling to speak in ", tongue_to_use, "."); endif .
-------------------------------------------------------------------- $generic_monster:unprovoked_attack this none this permissions: rxd player_obj = player; monster_obj = this; if(monster_obj.game_space != player_obj.game_space); return; endif {tongue_to_use, player_initiated, player_tactic} = args; tactics = {"tactic1", "tactic2", "tactic3", "etc."}; tongues = {"tongue1", "tongue2", "tongue3", "etc."}; if(player_tactic == "") player_tactic=tactics[random(3)]; endif if(tongue_to_use == "") tongue_to_use = tongues[random(3)]; endif known_tongues = {}; for tongue in (tongues) if(player_obj.(tongue) > 0) known_tongues = listappend(known_tongues, tongue); endif endfor player_damage = random(5); if(player_damage == 1) player_damage_str = "hitpoint"; else player_damage_str = "hitpoints"; endif attack_successful = random(100) <= ((15) + monster_obj.(tongue_to_use)); if (player.hp <= player.wimpout && player.hp > 0) return; endif if(monster_obj.hp < 1) return; endif if (attack_successful) player:tell("You got verbally assaulted by ", monster_obj.name, " in ", tongue_to_use, "!"); player:tell("You lost ", player_damage, " ", player_damage_str, "!"); player.location:announce_all_but({monster_obj, player}, monster_obj.name, " made ", player.name, " see things from ", player.pp, " point of view!"); player:hp_modify(-player_damage, monster_obj); endif monster_obj.last_attack = time(); fork(7) if(monster_obj.location == player_obj.location) if(monster_obj.last_attack + 6 < time()) monster_obj:unprovoked_attack(tongues[random(6)], 0, tactics[random(9)]); endif endif endfork .
MONSTER PROPERTIES
Most of the property values are scores generated on monster creation that get evaluated in relation to the current combatant's stats in effort to make the monster a worthy opponent. There could be any number of tactics, tongues, or property values that get set in this fashion. It is assumed you will be able to get the basic idea and extend as needed.
$generic_monster.(tactic) [proficiency in combat tactic - percent] $generic_monster.(tongue) [proficiency in combat tongue - percent] $generic_monster.str [strength - integer] $generic_monster.str_max [strength max - integer] $generic_monster.int [intelligence - integer] $generic_monster.int_max [intelligence max - integer] $generic_monster.wis [wisdom - integer] $generic_monster.wis_max [wisdom max - integer] $generic_monster.dex [dexterity - integer] $generic_monster.dex_max [dexterity max - integer] $generic_monster.chr [charisma - integer] $generic_monster.chr_max [charisma max - integer] $generic_monster.str_bon [strength bonus - integer] $generic_monster.int_bon [intelligence bonus - integer] $generic_monster.wis_bon [wisdom bonus - integer] $generic_monster.dex_bon [dexterity bonus - integer] $generic_monster.chr_bon [charisma bonus - integer] $generic_monster.funds [amount of available money - integer] $generic_monster.exp [experience points - integer] $generic_monster.exp_table [experience point breakdown for levels - {"list"}] $generic_monster.lev [level based on experience points - integer] $generic_monster.status [ranking based on level - "string"] $generic_monster.hp [currently available hitpoints - integer] $generic_monster.hp_max [maximum hitpoints - integer] $generic_monster.wimpout [hitpoint value determining when to run from combat - integer] $generic_monster.combat_wins [battles won - integer] $generic_monster.combat_losses [battles lost - integer] $generic_monster.last_attack [timer value set to determine next attack - integer] $generic_monster.game_space [keyed to player's on entry - "string"] $generic_monster.player_status_cp [status list used in specific quest space - {"list"}] $generic_monster.player_status_fs [status list used in specific quest space - {"list"}] $generic_monster.player_status_si [status list used in specific quest space - {"list"}] $generic_monster.home [where the monster gets sent to on death - obj#] examples of list properties: $generic_monster.exp_table - {1500, 12000, 40500, etc.} $generic_monster.player_status_cp/fs/si - {"initiate","convert","adherent","etc."}
BARTER BOT VERBS AND PROPERTIES
BARTER BOT VERBS
Here we have examples of the verb code for the special $bot type we created called the '$barter_bot.' It starts with the 'buy' verb for when the $player agent tries to buy something from the $barter_bot, followed with the 'sell' verb for when the $player agent tries to sell something in order to gain more funds.
-------------------------------------------------------------------- $barter_bot:buy g*et any out of/from inside/from this permissions: rd if ((dobj = this:match_object(dobjstr)) == $nothing) player:tell("What do you want to take from ", this.name, "?"); else " Check from generic list first. "; buying_warez = 0; warez_list_idx = 0; for warez_item in (this.warez_list) warez_list_idx = warez_list_idx + 1; {warez_name, warez_thing, warez_count} = warez_item; if (dobjstr == warez_name) buying_warez = 1; if (warez_count == 0) player:tell("I'm out of that. Try again later."); return; endif goods = {}; for m in (player.contents) if ($object_utils:isa(m, $tradeable_object)) goods = listappend(goods, m.name); if (warez_name == m.name) player:tell("You don't deserve another ", warez_name, "!"); return; endif endif endfor player:tell("So, you want to acquire a ", warez_name, " eh?"); negotiated_price = this:negotiate_sell(warez_name, warez_thing.msrp); if ((negotiated_price > 0.0) && (player.funds > negotiated_price)) " Sucessful sale. "; this.warez_list[warez_list_idx] = {warez_name, warez_thing, warez_count - 1}; player:tell("Sold for ", $string_utils:format_funds(negotiated_price), "."); player:adjust_funds(0.0 - negotiated_price); this.funds = this.funds + negotiated_price; warez_clone = player:_create(warez_thing); warez_clone.name = warez_name; warez_clone.aliases = {warez_name}; $wiz_utils:set_owner(warez_clone, player); warez_clone:moveto(player); endif endif endfor if (buying_warez == 0) " Try buying from inventory. "; if ($command_utils:object_match_failed(dobj, dobjstr)) elseif (!(dobj in this:contents())) player:tell("I have no ", dobj.name, "!"); else if ($object_utils:isa(dobj, $tradeable_object)) negotiated_price = this:negotiate_sell(dobj.name, dobj.msrp); if ((negotiated_price > 0.0) && (player.funds > negotiated_price)) "Sucessful sale"; player:tell("Sold for ", $string_utils:format_funds(negotiated_price), "."); player:adjust_funds(0.0 - negotiated_price); this.funds = this.funds + negotiated_price; $wiz_utils:set_owner(dobj, player); dobj:moveto(player); if (dobj.location == player) player.location:announce(player.name, " just acquired something from ", this.name, "."); endif endif else player:tell(dobj.name, " is not for sale!"); endif endif endif endif .
-------------------------------------------------------------------- $barter_bot:sell any at/to this permissions: rd if ((this.location != player) && (this.location != player.location)) player:tell("You can't get ", this.name, "'s attention."); elseif (dobj == $nothing) player:tell("What do you want to sell ", prepstr, " ", this.name, "?"); elseif ($command_utils:object_match_failed(dobj, dobjstr)) elseif ((dobj.location != player) && (dobj.location != player.location)) player:tell("You don't have ", dobj.name, "."); else if ($object_utils:isa(dobj, $tradeable_object)) negotiated_price = this:negotiate_buy(dobj.name, dobj.msrp); if ((negotiated_price >= 0.0) && (this.funds >= negotiated_price)) "Sucessful purchase"; player:tell("Purchased for ", $string_utils:format_funds(negotiated_price), "."); player:adjust_funds(negotiated_price); this.funds = this.funds - negotiated_price; player:tell("You'll never see that ", dobj.name," again!"); player.location:announce(player.name, " just got rid of ", player.pp, " ", dobj.name,"..."); player:moveto(dobj); else player:tell("Negotiation failed! I only have ", $string_utils:format_funds(this.funds), " left..."); endif else player:tell("I can't use that!!!"); endif endif .
Here is the internally executed code for the $barter_bot:buy and :sell verbs that gets called when the $player executes the verbs above. It starts with the 'negotiate_sell' verb, and is followed by the 'negotiate_buy' verb.
-------------------------------------------------------------------- $barter_bot:negotiate_sell this none this permissions: rxd {item_name, price} = args; player:tell("Negotiating for ", item_name, " at a starting price of ", $string_utils:format_funds(price), "."); ans = 0.0; half_price = price / 2.0; while (ans < price) player:tell("How much do you think it's worth?"); ans_str = read(player); ans = tofloat(ans_str); if (ans > player.funds) player:tell("Hmmm...you don't appear to have the necessary funds..."); return 0.0; elseif (ans < half_price) player:tell("Hah! As if!"); return 0.0; elseif (ans < price) player:tell("Now you're getting warmer..."); endif endwhile player:tell("You got yourself a deal!"); return ans; .
-------------------------------------------------------------------- $barter_bot:negotiate_buy this none this permissions: rxd {item_name, price} = args; player:tell("Negotiating for ", item_name, " at a starting price of ", $string_utils:format_funds(price), "."); double_price = price * 2.0; ans = double_price; while (ans > price) player:tell("How much do you want for it?"); ans_str = read(player); ans = tofloat(ans_str); if (ans == 0.0) player:tell("OK, but I had no idea you were so stupid...ummm...I mean so generous!"); return 0.0; endif if (ans > double_price) player:tell("Hah! As if!"); return 0.0; elseif (ans > price) player:tell("Now you're getting warmer..."); endif endwhile return ans; .
Finally, we have the verb code that generates a neatly formatted table listing of the $tradeable_objects the $barter_bot has available for agents to acquire.
-------------------------------------------------------------------- $barter_bot:list this none this permissions: rxd col_width = 68; my_objects = {}; "add inventory items"; for available_object in (this:contents()) if ($object_utils:isa(available_object, $tradeable_object)) my_objects = listappend(my_objects, available_object.name); endif endfor " Add generic items that are sold. "; " Show list to player. "; row1 = {{"Items", "left", 34},{"Remaining", "right", 17},{"MSRP", "right", 17}}; player:tell(""); player:tell("Carrying:"); player:tell($string_utils:space(col_width, "-")); player:tell($string_utils:flex_col_format(row1)); player:tell(""); for warez_item in (this.warez_list) {warez_name, warez_thing, warez_count} = warez_item; if (warez_count > 0) my_objects = listappend(my_objects, warez_name); endif row2 = {{warez_name, "left", 34},{$string_utils:from_value(warez_count), "right", 17},{$string_utils:format_funds(warez_thing.msrp), "right", 17}}; player:tell($string_utils:flex_col_format(row2)); endfor player:tell($string_utils:space(col_width, "-")); player:tell($string_utils:format_two_col(this.name + " funds: " + $string_utils:format_funds(this.funds), player.name + " funds: " + $string_utils:format_funds(player.funds), col_width)); fork(2) player:tell("Make an offer on something: 'buy [item] from ", this.name, "'..."); endfork .
BARTER BOT PROPERTIES
The $barter_bot only has a couple of properties to be concerned with:
$barter_bot.warez_list [colection list of items for bartering - {"item", obj#, amt}] $barter_bot.funds [amount of available money - integer]
SPACE VERBS AND PROPERTIES
SPACE VERBS
The check for encounter verb gets called when a space is entered (on the 'space:enterfunc'), at which point it proceeds to randomly select and create a "monster" type from a pre-defined list. It also checks if the room has been flagged as dangerous, as well as how many other monsters are in the same space. If it finds duplicates it removes them. It then gives the newly spawned monster a bunch of properties generated largely in relation to the player properties, and sticks it in the same space, at which point the battle begins whether provoked or not.
-------------------------------------------------------------------- $room:check_for_encounter this none this permissions: rxd {current_player, current_room} = args; name = {"the monster1", "the monster2", "the monster3", "etc."}; if (current_room.room_type == "dangerous") monsters_max = current_room.monsters_max; monsters_here = {}; for m in (current_room.contents) if ($object_utils:isa(m, $generic_monster)) monsters_here = listappend(monsters_here, m); endif endfor if (length(monsters_here) < monsters_max) possible_monster_names = {}; if ((current_player.game_space == "something") && (current_room.monsters_max > length(name))) current_room.monsters_max = length(name); possible_monster_names = name; elseif (current_player.game_space == "something") possible_monster_names = name; endif for monster_here in (monsters_here) for used_name in (monsters_here) possible_monster_names = setremove(possible_monster_names, "the " + used_name); endfor endfor if (length(possible_monster_names) > 0) nasty = current_player:_create($generic_monster); nasty.game_space = current_player.game_space; " This is where monster stats are set in relation to player stats "; nasty.prop1 = current_player.prop1; nasty.prop2 = current_player.prop3; nasty.prop3 = current_player.prop4; nasty.name = possible_monster_names[random(length(possible_monster_names))]; nasty.aliases = {nasty.name[5..$]}; current_level = current_player.lev; for exp_required in (nasty.exp_table) if (nasty.exp > exp_required) current_level = current_level + 1; endif endfor nasty.lev = current_level; nasty:update_status(); nasty.hp_max = current_player.hp_max + random(current_player.wis); nasty.hp = (current_player.hp_max / 2 + random(current_player.int)); move(nasty, current_room); monsters_here = listappend(monsters_here, nasty); fork (2) current_player:tell("You're being attacked by ", nasty.name, "!"); endfork endif endif for nasty in (monsters_here) fork (3) if (nasty.location == player.location) nasty:unprovoked_attack("", 0, ""); endif endfork endfor endif .
SPACE PROPERTIES
In order for the above to happen, a couple of properties are needed:
$room.room_type [set to either "normal" or "dangerous"] $room.monsters_max [set to whatever max number you want in the space at once]
UTILITY VERBS
The first utility verb we'll look at allows you to custom format a two column table layout. To see an example of what it looks like in use, check out the player stats screen in the combat system overview.
-------------------------------------------------------------------- $string_utils:format_two_col this none this permissions: rxd " This formats two strings into a left & right justified col. "; " Syntax: format(left_string, right_string, total_column_width); "; col1_string = args[1]; col2_string = args[2]; total_width = args[3]; crop_col_left = total_width / 2; crop_col_right = total_width - crop_col_left; if (length(col1_string) + length(col2_string) > total_width && length(col1_string) > crop_col_left) col1_string = col1_string[1..crop_col_left]; endif if (length(col1_string) + length(col2_string) > total_width && length(col2_string) > crop_col_right) col2_string = col2_string[1..crop_col_right]; endif spaces_required = total_width - length(col1_string) - length(col2_string); if (spaces_required > 0) col1_string = col1_string + $string_utils:space(spaces_required); endif formatted_string = col1_string + col2_string; return formatted_string; . example: col_width = 68; player:tell($string_utils:format_two_col("something","something",col_width));
This verb allows you to custom format a multi-column table layout. To see an example of what it looks like in use, check out the player skills screen in the combat system overview.
-------------------------------------------------------------------- $string_utils:flex_col_format this none this permissions: rxd " This formats a string based on list of {string, justification, width} "; formatted_string = ""; colprefs = args[1]; for colpref in (colprefs) {s, just, width} = colpref; if (length(s) > width) s = s[1..width]; endif if (just == "right") s = $string_utils:right(s, width); elseif (just == "center") s = $string_utils:center(s, width); else s = $string_utils:left(s, width); endif formatted_string = formatted_string + s; endfor return formatted_string; . example: col_width = 68; row1 = {{"something", "left", 8},{"something", "right", 5},{"something", "left", 10},{"", "", 13}}; player:tell($string_utils:flex_col_format(row1));
The next several verbs allow you to custom format number layouts into percentages, dollars and cents, and reduce values with multiple decimal places.
-------------------------------------------------------------------- $string_utils:to_percent this none this permissions: rxd " This returns a value followed by a % "; n = args[1]; s = $string_utils:from_value(n); s = s + "%"; return s; .
-------------------------------------------------------------------- $string_utils:format_funds this none this permissions: rxd " This returns a dollars and cents layout -- $0.00"; n = args[1]; whole_part = $math_utils:trunc(n / 100.0) * 100.0; whole_part_str = $string_utils:from_value(whole_part); num_whole_digits = length(whole_part_str) - 2; whole_part_str = whole_part_str[1..num_whole_digits]; dec_part = $math_utils:trunc(n % 1.0) * 100.0; dec_part_str = $string_utils:from_value(dec_part); if (dec_part == 0.0) dec_part_str = "00"; elseif (dec_part < 10.0) dec_part_str = "0" + dec_part_str[1..1]; else dec_part_str = dec_part_str[1..2]; endif s = "$" + whole_part_str + "." + dec_part_str; return s; .
-------------------------------------------------------------------- $math_utils:trunc this none this permissions: rxd " This truncs a value to two one decimal place -- 0.0 "; n = args[1]; d = 100.0; n = (d * n - d * n % 1.0) / d; return n; .
Please send comments to nideffer@uci.edu.