BeBot - An Anarchy Online and Age Of Conan chat automaton
Development => Generic custom modules => Topic started by: DJKRose on November 18, 2009, 07:34:04 pm
-
Hi folks!
I've made a flexible Text Adventure Module for BeBot: Create interactive text adventures and let your guild members in-game go from chapter to chapter by clicking on options that you have defined. You can make guild advertisement with it as well!
- Simple text files to define your text adventure
- Use name tags, includes, redirects
- Enabling creative guild advertisement
- For fun or serious stuff (crafting, instance information, ...)
Download: http://tubaka.tu.funpic.de/TextAdventure.zip
1) Extract it in your bot's root directory keeping the directory structure.
2) Test the bot with the command "!textadventure example"
3) See example.txt with lots of comments for creating your own adventures.
4) Put your own adventures in directory "textadventures" with .txt extension.
5) Have a look at the seetings (you don't need to change anything): "!settings TextAdventure"
Zip file also includes a big German text adventure called "tortage" which I use for guild advertisement sometimes.
Please post your comments and own text adventures in this thread (german welcome)!
Have fun,
djkrose
-
p.s.:
There is an undocumented feature: Prepend a chapter line with "-->" to allow lateral entrance to this chapter. Useful if you want to include the first choice command in global guild spam or other scripts. Example:
--> 5. You are entering the room.
- Go right --> 6
- Go left --> 7
Directly jump into chapter 5 and trigger choice 7 with this command: !textadventure filename 5 7
-
ohhhh a very nice module ... works perfekt ;) our rp-fanatics love it ... they are know writing storries ... maybe we would post them here ... (german ;) )
greez Drizz
-
Hi i just test your module, and when i start example and click on one answer i have this :
A [Mrbot] : !textadventure 1 10
[Mrbot]: /tell Mrbot !help
-
I make some translation for my french guildies and i suppose i remove or translate one var because i haven't this : !textadventure filename 1 10
i have this :
!textadventure 1 10
-
After somme test if i whrite !textadventure filename 1 10 --> work
I just want to give automatically <filename> in the generated link
I post my code :
<?php
/*
* Aventure RPG texte pour BeBot
* Version: 1.1
* Copyright (c) 2009 djkrose
* Modification / traduction par Gauche
* Change log:
* v1.1 - Traduction en français par Gauche pour la guilde Vigilance sur Ishtar.
* v1.0 - First Release version!
* - Fixed some formatting.
* v0.2 - Added possibility to have multiple choices with same target.
* - Removed chapter text in info blob.
* - Added possibility to include html tags
* - Added support for ##include=file.txt##
* - Added setting to allow lateral entrance from every chapter.
* - Added support to define lateral entrance to particular chapters using "-->" before the chapter number.
* v0.1 - First working version.
*/
$rpgaventure = new RpgAventure($bot);
class RpgAventure extends BaseActiveModule {
private $AVENTURE_FILE_DIRECTORY = "./rpgaventures";
private $MSG_ONLY_CLICK_ONCE = "Un clic suffit! (soyez patient)";
private $progress = array();
private $adventures = array();
private $cooldown = array();
private $file_times = array();
function __construct(&$bot) {
parent::__construct($bot, get_class($this));
// Register commands
$this -> register_command('all', 'rpgaventure', 'GUEST');
// Create settings
$this -> bot -> core("settings") -> create("RpgAventure", "TimeOut", 5,
"Après combien de minutes le status du chapitre en cour doit être reset?", "1;2;3;4;5;10;20;30;60");
$this -> bot -> core("settings") -> create("RpgAventure", "CoolDown", 5,
"Après combien de minutes les joueurs peuvent retenter une aventure?", "1;2;3;4;5;10;20;30;60");
$this -> bot -> core("settings") -> create("RpgAventure", "LateralEntrance", FALSE,
"Autoriser les joueurs a commencer depuis n'importe quel chapitre, avec la commande \"-->\" marker?");
// Set help texts
$this -> help['description'] = 'Permet aux utilisateurs de réaliser des RPG en mode texte.';
$this -> help['command']['rpgaventure <exemple>']="Commencer l'aventure RPG nommé <exemple>.";
$this -> help['notes'] = "Les admins peuvent modifier les paramétres sur les timers avec la commande !settings.";
}
function command_handler($name, $msg, $origin) {
// Process input command
if (preg_match('/^rpgaventure +([a-zA-ZöäüßÖÄÜ_-]+) +([0-9]+) +([0-9]+) *$/i', $msg, $matches))
$reply = $this->process_step($name, $matches[1], (int)$matches[2], (int)$matches[3]);
elseif (preg_match('/^rpgaventure +([a-zA-ZöäüßÖÄÜ_-]+) *$/i', $msg, $matches))
$reply = $this->process_step($name, $matches[1]);
else
$this->bot->send_help($name);
// Send reply via tell, if any
if ($reply)
$this->bot->send_tell($name, $reply);
}
/**
* Supprimer tous les éléments de $this->Temps dépassé.
*/
function clear_timed_out_progress() {
$time_out = $this->bot->core("settings")->get("RpgAventure", "TimeOut");
$time = time();
foreach($this->progress as $key=>$value) {
if ($value['time_stamp'] + $time_out*60 < $time)
unset($this->progress[$key]);
}
}
/**
* Supprimer tous les éléments de $this->Temps de recharge dépassé.
*/
function clear_timed_out_cooldown() {
$time_out = $this->bot->core("settings")->get("RpgAventure", "CoolDown");
$time = time();
foreach($this->cooldown as $key=>$value) {
if ($value + $time_out*60 < $time)
unset($this->cooldown[$key]);
}
}
/**
* Rappel pour preg_replace_callback qui est appelée à chaque include trouvé dans le texte
* de l'aventure. Attention risque de faille niveau sécurité! Ne jamais utiliser d'aventure non vérifié,
* car la fonction include peut charger n'importe quel fichier!
*
* @param $matches Utilisation de l'expression matches; le premier élément correspond
* au nom du dossier relatif a l'aventure.
* @return Retourne le contenu d'un fichier ou une chaîne vide en cas d'erreur.
*/
function include_callback($matches) {
$content = @file($this->AVENTURE_FILE_DIRECTORY."/".$matches[1]);
if ($content === FALSE)
return "";
else
return join("", $content);
}
/**
* Makes sure, that the given adventure is properly parsed and ready to use in $this->aventures.
* If it's not yet ready or if the source file has changed, it will be (re)parsed.
*
* @param $aventure The text aventure name to parse.
* @return FALSE on any parsing errors
*/
function prepare_aventure($aventure) {
// Init some variables and values
$curradv =& $this->aventures[$aventure];
$aventure = preg_replace('/[^a-zA-ZöäüßÖÄÜ_-]/', '', $aventure); // Extra double security
$filename = "$aventure.txt";
$filepath = $this->AVENTURE_FILE_DIRECTORY."/".$filename;
// Don't re-load file if file hasn't been changed since last parsing
clearstatcache();
$file_time = filemtime($filepath);
if (is_array($curradv) && $this->file_times[$filepath] >= $file_time)
return TRUE;
$this->file_times[$filepath] = $file_time;
// Load file contents
$curradv = array();
$lines = @file($filepath);
if ($lines === FALSE) {
$this->bot->log("RpgAventure", "ERROR", "Le fichier aventure $filename n'a pu être trouvé.");
return FALSE;
}
// Process includes; since included content can have line breaks, the new lines must be inserted in $lines.
for ($i=0; $i<count($lines); $i++) {
$newlines = preg_replace_callback('/##include=([^#]+)##/', array($this, "include_callback"), $lines[$i], -1, $count);
if ($count > 0) {
$newlines = preg_split('/(\r\n|\n|\r)/', $newlines);
$lines_before = array_slice($lines, 0, $i);
$lines_after = array_slice($lines, $i+1);
$lines = array_merge($lines_before, $newlines, $lines_after);
$i--;
}
}
// Parse file line by line
$chapter_mode = FALSE;
$number = 0;
foreach ($lines as $line) {
$number++;
$line = trim($line);
// Ignore empty lines and comments
if (empty($line) || $line{0} == "#")
continue;
// Parse chapter number and its first text
if (preg_match('/^(-->)?\s*([0-9]+)\.\s*(.*)$/', $line, $matches)) {
$current_chapter = 2;
$curradv_chapter =& $curradv[(int)$matches[2]];
if (isset($curradv_chapter)) {
$this->bot->log("RpgAventure", "ERROR", "$filename:$number - 2 chapitres avec le même numéro trouvé!");
return FALSE;
}
$curradv_chapter['chapter_text'] = $matches[3];
// Set flag that lateral entrance to this chapter is allowed, if chapter line starts with "-->"
if ($matches[1] == "-->")
$curradv_chapter['lateral_entrance'] = TRUE;
$chapter_mode = TRUE;
}
// Parse choice
elseif (preg_match('/^-\s*(.+?)\s*-->\s*([0-9]+)$/', $line, $matches)) {
if (!isset($curradv_chapter)) {
$this->bot->log("RpgAventure", "ERROR", "$filename:$number - Choix non autorisé ici!");
return FALSE;
}
// Choices are collected in an extra array because we could have more than one choice with same target
$curradv_chapter['choices'][(int)$matches[2]][] = $matches[1];
$chapter_mode = FALSE;
}
// Parse redirect
elseif (preg_match('/^-->\s*([0-9]+)$/', $line, $matches)) {
if (!$chapter_mode) {
$this->bot->log("RpgAventure", "ERROR", "$filename:$number - Redirection non autorisé ici!");
return FALSE;
}
$curradv_chapter['redirect'] = (int)$matches[1];
unset($curradv_chapter); // unset the reference, not the actual array content!
$chapter_mode = FALSE;
}
// Parse additional chapter texts
elseif ($chapter_mode == TRUE) {
$curradv_chapter['chapter_text'] .= "\n$line";
}
else {
$this->bot->log("RpgAventure", "ERROR", "$filename:$number - Je ne sais pas gérer cette ligne!");
return FALSE;
}
}
// Check for existence of chapter 1
if (!isset($curradv[1])) {
$this->bot->log("RpgAventure", "ERROR", "$filename ne contient pas de chapitre 1. Ceci est obligatoire pour commencer l'aventure!");
return FALSE;
}
// Check if all choice targets exist as chapters
foreach ($curradv as $chapter_nr=>$chapter) {
if (!is_array($chapter["choices"]))
continue;
foreach ($chapter["choices"] as $target=>$text) {
if (!isset($curradv[$target])) {
$this->bot->log("RpgAventure", "ERROR", "$filename - Chapitre $chapter_nr contient le choix $target, mais il n'existe pas!");
return FALSE;
}
}
}
$this->bot->log("RpgAventure", "PARSE", "RPG aventure $filename chargé.");
return TRUE;
}
/**
* Verifier ou executer la prochainne (ou premiere) étape dans l'aventure donnée selon le
* $chapter et $choice. La prochainne étape est annoncer directement a l'utilisateur, toutes erreur est * retournée sous forme de chaîne et annoncé a l'utilisateur
* Fonction est appelée directement par command_handler(..). Toutes les commandes nécessaires pour charger * l'aventure, vérifier les erreurs et cheat, les cd et les temps dépassé se font ici aussi.
*
* @param $name Le nom de l'utilisateur executant la commande rpgaventure
* @param $aventure Le nom de l'aventure demandé en tant que chaîne
* @param $chapter Le numéro du chapitre en cours, à savoir ou est l'utilisateur dans l'histoire en ce * moment
* @param $choice La cible choisit (numéro du prochain chapitre), ou l'utilisateur veut aller
* @return Une chaîne envoyer a l'utilisateur en tell, ou rien du tout ce qui n'est pas plus mal^^.
*/
function process_step($name, $aventure, $chapter = FALSE, $choice = FALSE) {
// Init progress and aventures
$this->clear_timed_out_progress();
$this->clear_timed_out_cooldown();
$curradv =& $this->aventures[$aventure]; // shortlink for the current adventure content
$currpro =& $this->progress["$name:$aventure"]; // shortlink for the user's progress on current aventure
$currcool =& $this->cooldown["$name:$aventure"]; // shortlink for the user's cool down on current aventure
if (!$this->prepare_aventure($aventure))
return "##error##RPG aventure ##highlight##$aventure##end## n'a pu être trouvé ou a une erreur.##end##";
// Check if progress timed out
// (time out error in lateral entrance mode is not possible, because the user can start from everywhere)
$is_lateral_entrance_everywhere = $this->bot->core("settings")->get("RpgAventure", "LateralEntrance");
$is_lateral_entrance_here = ($choice !== FALSE && $curradv[(int)$choice]['lateral_entrance']);
if ($chapter !== FALSE && !isset($currpro) && !($is_lateral_entrance_everywhere || $is_lateral_entrance_here))
return "##error##Votre progression sur cette aventure a expirée ou vous l'avez terminé. Recommencer!##end##";
// Check if user tries to re-do steps or cheat
if ($chapter !== FALSE && isset($currpro) && $chapter != $currpro["chapter"]) {
if ($chapter == $currpro["last_chapter"] && $choice == $currpro["last_choice"])
return; // assume double click and ignore it
else
return "##error##Votre choix n'est pas autorisé.Vous ne pouvez pas refaire une étape ou tricher!";
}
// Check if user tries to start over when not finished yet
if ($chapter === FALSE && isset($currpro)) {
if (!$currpro["last_chapter"])
return; // assume double click on start link, so we ignore it
else
return "##error##Vous ne pouvez pas recommencer, Vous n'avez pas fini!##end##";
}
// Check if choice is possible
if ($chapter !== FALSE && !isset($curradv[(int)$chapter]["choices"][(int)$choice]))
return "##error##Choix impossible!##end##";
// Check if user tries to start adventure when cooldown active
if (!isset($currpro) && isset($currcool))
return "##error##Vous ne pouvez recommencer cette aventure pour le moment, cool down encore actif##end##";
// If this adventure was started right now, activate the cool down
if (!isset($currpro))
$currcool = time();
// Depending on users choice start with either chapter 1 or with his selection
if ($choice === FALSE) {
$currpro["chapter"] = 1;
} else {
$currpro["last_chapter"] = (int)$chapter;
$currpro["last_choice"] = (int)$choice;
$currpro["chapter"] = (int)$choice;
}
unset($chapter); // does not contain current value any more
unset($choice); // does not contain current value any more
// Make chapter text
$text = $curradv[$currpro["chapter"]]["chapter_text"];
// Follow redirects
$i = 0;
while (isset($curradv[$currpro["chapter"]]["redirect"])) {
$i++;
if ($i > 50) {
$this->bot->log("RpgAventure", "ERROR", "Il semble que l'aventure $adventure a une redirection infini vers le chapitre {$currpro['chapter']}!");
$currpro = NULL;
return "##error##Une erreur s'est produite lors du traitement de votre demande. S'il vous plaît informer Gauche!##end##";
}
$currpro["chapter"] = $curradv[$currpro["chapter"]]["redirect"];
}
// Add choice text as info blob
if (isset($curradv[$currpro["chapter"]]["choices"]) && count($curradv[$currpro["chapter"]]["choices"]) > 0) {
// $text_for_blob = preg_replace('/{([^{}]+)}/', '##highlight##$1##end##', $text);
// $text_for_blob = htmlspecialchars(strip_tags($text_for_blob));
// $text_for_blob = str_replace("\n", "<br>", $text_for_blob);
$info_blob = "##blob_title## ::::: RPG AVENTURE :::::<br></><br>";
// $info_blob .= "##blob_text##$text_for_blob<br>";
$info_blob .= "##blob_text##";
$i = 0;
foreach ($curradv[$currpro["chapter"]]["choices"] as $target_id=>$target_texts) {
// One target can have multiple texts, which is why it is always an array of strings
foreach ($target_texts as $target_text) {
$i++;
$info_blob .= "$i. <a href='chatcmd:///tell {$this->bot->botname} <pre>rpgaventure " .
"$adventure {$currpro['chapter']} $target_id'>" . htmlspecialchars($target_text) . "</a><br>";
}
}
$info_blob .= "</><br>" . $this->MSG_ONLY_CLICK_ONCE;
$info_blob = str_replace("\"", """, $info_blob);
$text = preg_replace('/{([^{}]+)}/', "<a href=\"text://$info_blob\">\$1</a>", $text);
}
// Insert user's name for ##name##
$text = str_replace("##name##", $name, $text);
// Send multi-line text as multiple tells
$text = explode("\n", $text);
foreach ($text as $msg)
$this->bot->send_tell($name, $msg);
// If finished, clear progression
if (!is_array($curradv[$currpro["chapter"]]["choices"]) || count($curradv[$currpro["chapter"]]["choices"]) == 0)
$currpro = NULL;
// If not finished, update time stamp
else
$currpro["time_stamp"] = time();
}
}
?>
-
the download link doesnt work
-
I found one error in my script i whrite this : private $adventures = array(); i change in private $aventures = array();
But i have the same error.
Edit : Ok i'm big noob... i define $aventures and i use $aventure...i haven't test but i'm sure it's ok^^
-
Got this one up and working with no problems and I really like the potential of it, but is it possible to get all things happening inside the popup window instead of a combination of chat box and window?