Difference between revisions of "Tintin"

From Flexible Survival
Jump to: navigation, search
m (Stitching void rooms within a grid.)
 
(7 intermediate revisions by 2 users not shown)
Line 1: Line 1:
For anyone who wishes to use TinTin++ here are a few handy Flexible Survival scripts and accompanying python scripts which you can use (or cut and paste out of) that I've written and refined over the past few years.
+
==Install==
  
The commands that follow should be copied into their own files in a folder of your choosing. My scripts are in a folder called tt in my home directory. I then use tmux to split my window into four sections:
+
===Required packages (for Debian Linux)===
Top left is input and everything coming from the mud.
 
Top right is all players, pets, enemies and enemy pet health (updated by parsing fprompt).
 
Bottom left is all chat-related stuff.
 
Bottom right is powers used (with damage), XP collected, tokens and salvage gathered, daily mission completion status and other misc battle-related stuff.
 
  
Here's what it looks like when it's running:<br>
+
Become root, either via '''sudo -i''' or some other method.
[[File:Screenshot_2015-08-13_13-00-12.png|900px]]
 
  
 +
Run all of these commands as '''root''' (yes, bash is probably pre-installed, this is just making sure).
  
I have a bash script (anwen_mud.sh) that starts tmux and configures the four sections:
+
apt-get update
<pre>
+
apt-get install tmux tintin++ bash git nano \
#!/bin/bash
+
libcommon-sense-perl \
cd ~/tt
+
libdatetime-perl \
 +
libdatetime-event-sunrise-perl \
 +
libfile-changenotify-perl \
 +
libfile-pid-perl \
 +
libjson-perl \
 +
libtest-lwp-useragent-perl \
 +
libpath-class-perl \
 +
libtime-duration-perl
  
tmux -2 new-session -d -s 'mud'
+
Type '''exit''' (or ctrl+d) to end the session if you opened a new root session for this.
  
tmux new-window -t 'mud':1 -n 'mud'
+
FIXME: Are Getopt::Long and Term::ANSIColor built in, there don't seem to be Debian packages.
tmux split-window -h -p 26
 
tmux select-pane -t 0
 
tmux send-keys "tt++ anwen_start" C-m
 
tmux select-pane -t 1
 
tmux send-keys "tail -f anwen_hp 2>/dev/null" C-m
 
tmux select-pane -t 0
 
tmux split-window -v
 
tmux send-keys "tail -f anwen_chatlog" C-m
 
tmux select-pane -t 2
 
tmux split-window -v
 
tmux send-keys "tail -f anwen_stats" C-m
 
tmux select-pane -t 0
 
tmux -2 attach-session -t 'mud'
 
</pre>
 
  
 +
===Download the script package===
  
Generic connection script, note that this is linux based so the log file location needs to be changed if you're using WinTin in linux I save it with the name 'charname_start':
+
Next, run these commands as your normal user account (not root).  Replace any bold names with file-paths of your choosing.  Also remember that once a path exists, tab-complete often works for typing it out.
<pre>
 
#nop {==================================================================}
 
#nop {Python stuff}
 
#nop {==================================================================}
 
#run py python
 
#py import fs_python as fs
 
  
#py fs.config.player = "CHARNAME"
+
cd '''AnywhereYouWant'''
#py print(fs.config.player)
+
git clone https://gitlab.com/inutt/flexible-survival-ui.git '''fs-ui'''
#py fs.read_vars()
+
chmod +x '''fs-ui'''/flexible_survival
#py import time
+
'''fs-ui'''/flexible_survival
  
#action {tintin (%1) %2} {#%1 %2}
+
===Very minimal config.json===
  
#nop {==================================================================}
+
nano '''fs-ui'''/config.json
#nop {End of python stuff}
 
#nop {==================================================================}
 
  
#alias {start_fs} {#session fs flexiblesurvival.com 2000}
+
Above nano is used to edit the config file, if you prefer a different editor use that.  The launcher script should have setup the config file to be readable only by admins (root) and your exact user account.
start_fs
 
  
#split
+
* '''"character": "",''' and
 +
* '''"password": "",''' MUST be filled out.
  
#act {Welcome to Flexible Survival:}
+
JSON ignores non-printing 'whitespace' and requires that a key/value sets (contained in { } and called an object) have values in the middle separated by commas, but NO comma at the end.  {"character": "USER", "password": "PASSWORD"}.  Any values you don't want to change can be deleted.  Thus this might be an example minimal configuration file:
{
 
    #unticker {reconnect};
 
    #send connect CHARNAME YOUR PASSWORD;
 
    #var connected 1;
 
    #read anwen_scripts
 
}
 
</pre>
 
  
Next are those functions and values unique to my characters that override those in the scripts_all file that follows this one.
+
{
<pre>
+
    "fs": {
#var playername Anwen
+
        "character": "YOURUSER",
#highlight {Anwen} {light magenta}
+
        "password": "YOURPASS"
#highlight {anwen} {light magenta}
+
    }
 +
}
  
#var hp_log anwen_hp
+
In the above the extra commas after the password line, and that closing } after the "fs" key have been removed.  Again, the whitespace doesn't matter, so it could also be just (with no spaces even).
#var stats_log anwen_stats
 
#var chat_log anwen_chatlog
 
  
#nop {==================================================================}
+
{"fs":{"character":"YOURUSER","password":"YOURPASS"}}
#nop {Here we setup default variable values}
 
#nop {==================================================================}
 
#var messages 1
 
#var boton 0
 
#var bat 0
 
#var continue 0
 
#var ambushed 0
 
#var party_invite_status 1
 
#var party_invite_status_text Trusted
 
#var hptop 90
 
#var entop 90
 
#var submit 15
 
#var beeps 0
 
#var daily_status 0
 
#var current_location None
 
#var fallen 0
 
#var targeting 0
 
#var target_mode 0
 
#var enemy_list None
 
#var player_list None
 
#var down None
 
#var once_command nop
 
#var bound 0
 
#var load 0
 
#var completion
 
#var reward_lvl
 
#var alu_ic 0
 
  
 +
===Setting where the window splits to panes===
  
#alias {^dailys %5} {
+
The numbers in the '''tmux''' section are how you configure the split of the window.  You'll probably want to keep them and change them to suit your own needs.
    #send ooc Anwen !daily %5;
 
    #send ooc Hope !daily %5;
 
    #send ooc Aluthal !mission %5;
 
    #send ooc Skade !daily %5
 
}
 
#alias {^dailys %5} {#send ooc Anwen !daily %5; #send ooc Hope !daily %5; #send ooc Aluthal !mission %5; #send ooc Skade !daily %5}
 
  
#alias {^pets} {use bump;use grind;use Totemic deer}
+
    14 chat
#alias {^buff} {#send ooc Aluthal !buff}
+
  ------+-----------
 +
  status|
 +
  ------|  main
 +
  map 27|
 +
  64=>  |
 +
    "chat_height": 14,
 +
    "map_width": 64,
 +
    "map_height": 27,
 +
    "status_height": 16
  
#nop Read in scripts_all first then overwrite standard functions with those that follow.
+
Note: '''status_height''' doesn't actually set the height.  It tells the status update script how tall this section is so that it ''might'' omit some spacing or features to fit.
#read scripts_all
 
  
#nop CUSTOM STUFF FOLLOWS
+
===Final config.json===
#nop {==================================================================}
 
#nop {Redirects to chat_log}
 
#nop {==================================================================}
 
#act {^[{The Red Court|the red court}] %5$} {
 
    #line log {$chat_log}
 
} {2}
 
  
#nop {==================================================================}
+
You probably also want to set "data_dir": "SOMETHING", which is where your map data and log files will be stored.  This won't be created for you, so make sure you create it first. E.G.
#nop {sexytimes triggers}
 
#nop {==================================================================}
 
  
#nop {==================================================================}
+
mkdir "~/Documents/Flexible Survival Logs"
#nop {misc game and partying triggers}
 
#nop {==================================================================}
 
  
#nop {==================================================================}
+
Then your config.json file might look something like this
#nop {battle and ooc game triggers}
+
nano '''fs-ui'''/config.json
#nop {==================================================================}
 
  
#nop {==================================================================}
+
{
#nop {fighty time triggers}
+
    "tmux": {
#nop {==================================================================}
+
        "session_name": "Flexible Survival",
 +
        "chat_height": 14,
 +
        "map_width": 64,
 +
        "map_height": 27,
 +
        "status_height": 16
 +
    },
 +
    "fs": {
 +
        "character": "YOURUSER",
 +
        "password": "YOURPASS",
 +
        "data_dir": "~/Documents/Flexible Survival Logs"
 +
    }
 +
}
  
#act {^Aluthal goes [IC] In-Character.} {#var alu_ic 1}
+
If you want near-realtime weather, please sign up for [https://home.openweathermap.org/users/sign_up An OpenWeatherMap account] and make sure you retain and fill out the '''api_key''' with your account key. (NOT a login/password, but an account token that looks a bit like HEX00341AFEAAA0358 but longer, and probably actually all hex.)
  
#act {^<Random Encounter> $playername is ambushed!} {
+
If you're using WSL this gets more complicated.  "/mnt/c/Users/WindowsAccountName/My Documents/Flexible Survival Logs" might work for most.  If it doesn't, talk to whomever maintains the PC.  Tab-completion is really helpful here... mkdir /mnt/c/User (TAB) and then hit tab a couple more times to get a good guess at user account and My Documents locations to start in.
    #showme AMBUSHED;
 
    #var ambushed 1;
 
    #showme BATTLE STARTED;
 
    #var bat 1;
 
    #var alu_ic 0;
 
    #delay {ambushed} {#var ambushed 0} {60};
 
    #py fs.battle_timer("started")
 
}
 
  
#act {^<%6/%7 hp %10 /%11 m 0 /0 mv} {
+
With that setup you SHOULD be able to (after setup!)
    #var hpnow %6; #var hpmax %7;
 
    #var ennow %10; #var enmax %11;
 
    #math {hpper} {($hpnow/$hpmax) * 100.00};
 
    #math {enper} {($ennow/$enmax) * 100.00};
 
    #if {$boton == 1} {
 
        #if {$bat == 0} {
 
            #nop {#showme {NOT IN BATTLE}};
 
            #if {$hpper >= $hptop && $enper >= $entop && $ambushed == 0 && $alu_ic == 1} {
 
                #undelay {search_time};
 
                #delay {search_time} {#send search} {10}
 
            };
 
            #else {
 
                #undelay {waiting};
 
                #delay {waiting} {#send mprompt} {10}
 
            };
 
        };
 
        #else {
 
            #if {$hpper <= $submit} {
 
                #var alu_ic 1;
 
                #send submit $targeting
 
            };
 
        };
 
    };
 
    #format hitpoints {%cHP %d/%d%c} {light green} {$hpnow} {$hpmax} {white};
 
    #format energypoints {%cEN %d/%d%c} {light blue} {$ennow} {$enmax} {white};
 
    #if {$hpper < 100 || $enper < 100} {
 
        #showme STATUS: $hitpoints $energypoints $hpper $enper
 
    };
 
    time
 
}
 
  
#nop {==================================================================}
+
===Get updates===
#nop {Dump battle damage to stats_log.}
 
#nop {==================================================================}
 
  
#nop {==================================================================}
+
cd '''fs-ui'''
#nop {grab player stats from fprompt for logging.}
+
git pull
#nop {==================================================================}
+
 +
This can be run any time (in where-ever you had the download stored).  If there are any updates or bugfixes; though you might need to update your config.json file to use new features.
  
#nop {==================================================================}
+
==Connect==
#nop {Auto targeting}
 
#nop {==================================================================}
 
  
#nop {==================================================================}
+
* '''fs-ui'''/flexible_survival
#nop {Status stuff}
 
#nop {==================================================================}
 
  
</pre>
+
When running '''Ctrl+B (arrow key)''' (hold down control, tap b, release both; then press an arrow key) can be used to move the input-focus between panes (text areas within that window).  '''Up''' and '''Down''' arrow keys are the ones you'll care about.  Up to go to the chat channels, down to go back to muck input.  You type all commands in the main muck window, even though the text shows up above.
  
Last is a series of aliases, actions and other functions that are common to all of my characters:
+
==Automapping==
<pre>
 
#config {packet patch} {0.5};    #nop {Fixes rendering issues}
 
#message variable off
 
  
#gag nop
+
Inutt automated a lot of the mapping stuff, the 80% of it that's easy and not dependent on foreknowledge of inconsistencies.
#TICKER {keepalive} {#send nop; time; #send mprompt} {60}
 
  
#send {pose #prepend <ic>}
+
On perfectly grid like streets, where all of the rooms are pixels on a grid, it works very well.
#send {spoof #show}
 
#send {tick on}
 
#send {botmode off}
 
#format {bot_status} {%cBOT%c} {light green} {white}
 
  
#alias {^fp} {#send look}
+
===Undo===
#alias {^moff} {#var messages 0}
 
#alias {^mon} {#var messages 1}
 
#alias {^once %5} {#var once_command %5}
 
#alias {^gt %1} {#showme gothere %1}
 
#alias {^back} {#var bound 1; #py fs.edit_descrip("home")}
 
#alias {^tied} {#var bound 1; #py fs.edit_descrip("tied")}
 
#alias {^away} {#var bound 0; #py fs.edit_descrip("away")}
 
#alias {^fucked} {#py fs.secret($playername, "all")}
 
#alias {^add %5} {#py fs.party_list("add", "%5")}
 
#alias {^del %5} {#py fs.party_list("del", "%5")}
 
#alias {^party_check} {#py fs.party_list("check", "None")}
 
#alias {^daily?} {#py fs.daily_timer(); #var completion {}; #var reward_lvl {}}
 
#alias {^stat on} {#ticker {stats} {#send mprompt} {10}}
 
#alias {^stat off} {#unticker {stats}}
 
#alias {^pyes} {#var party_invite_status 2; #var party_invite_status_text Open}
 
#alias {^pno} {#var party_invite_status 0; #var party_invite_status_text Closed}
 
#alias {^ptrust} {#var party_invite_status 1; ; #var party_invite_status_text Trusted}
 
#alias {^beeps} {#var beeps 1}
 
#alias {^quiet} {#var beeps 0}
 
#alias {^pmentor %5} {partymentor %5}
 
#alias {^diff %5} {#send web #difficultyLevel %5}
 
#alias {^pher %5} {#send web #difficultyQuantity %5}
 
#alias {^danger} {ooc $playername !danger}
 
#alias {^up} {#var fallen 0;#var down None}
 
#alias {^boton} {#send ooc $playername !bot on}
 
#alias {^botoff} {
 
    #if {$bat == 1} {
 
        #send once ooc $playername !bot off
 
    };
 
    #else {
 
        once ooc $playername !bot off
 
    }
 
}
 
#alias {^botoff} {
 
    #if {$bat == 1} {
 
        once ooc $playername !bot off
 
    };
 
    #else {
 
        #send ooc $playername !bot off
 
    }
 
}
 
#alias {time}
 
{
 
    #format clock {%c%t} {light green} {%T};
 
    #showme {$clock}
 
}
 
  
#nop {==================================================================}
+
If you tried to go a given direction, and it wasn't a valid direction, you MAY need to undo. Inutt's main.tin script catches some, but not all, of these error messages.  Undo will move you back on the map and delete the most recently created room.
#nop {Redirects to chat_log}
 
#nop {==================================================================}
 
#act {^[{Auction|Bile|Bitch|Lfg|Links|Naughty|Newbie|Pickup|Public|Rp|Science|Tg|Update}] %5$} {
 
    #line log {$chat_log}
 
} {2}
 
#act {^<Give> %4 gives %5 to you.} {
 
    #format {give} {%c%t %c<%cGIFT%c> %c%s gave %s.} {white} {%T} {yellow} {cyan} {yellow} {white} {%4} {%5};
 
    #line log {$chat_log} {$give}
 
} {6}
 
#nop {#act {^[%2$} {#line log {$chat_log}} {2} }
 
  
#act {^<ic> %2$} {#format {ic} {%c%t %c<%cIC%c> %c%s} {white} {%T} {yellow} {cyan} {yellow} {white} {%2}; #line log {$chat_log} {$ic}}
+
#map undo
#act {^<OOC> %2$} {#format {ooc} {%c%t %c<%cOOC%c> %c%s} {light green} {%T} {yellow} {cyan} {yellow} {white} {%2}; #line log {$chat_log} {$ooc}}
 
#act {^[%5(#%6)]: %2$} {#format {spoof} {%c%t %c<%cSP%c> %c%s} {white} {%T} {yellow} {cyan} {yellow} {white} {%2}; #line log {$chat_log} {$spoof}}
 
  
#act {^You %, %2$} {#format {say} {%c%t %c<%cYS%c> %c%s} {light green} {%T} {yellow} {cyan} {yellow} {white} {%2}; #line log {$chat_log} {$say}} {6}
+
===Leaving the Grid (in a grid direction)===
#act {^You page, %2$} {#format {yp} {%c%t %c<<%cYP%c>> %c%s} {light green} {%T} {yellow} {cyan} {yellow} {white} {%2}; #line log {$chat_log} {$yp}} {5}
 
#act {^In a page-pose to $playername, %2$} {#format {pp} {%c%t %cIn a page-pose to you,%c %c%s} {light green} {%T} {yellow} {cyan} {white} {%2}; #line log {$chat_log} {$pp}}
 
#act {^%4 gives %5 to $playername.} {
 
    #format {give} {%c%t %c<%cGIFT%c> %c%s %s} {light green} {%T} {yellow} {cyan} {yellow} {white} {%4} {%5};
 
    #line log {$chat_log} {$give}
 
}
 
#act {^In a page-pose to you, %5 %6} {
 
    #format {ppose} {%c%t %cP-P %c%s} {light green} {%T} {cyan} {white} {%0};
 
    #nop {#format {ppose} {%c%t %cP-P %c%s %c%s} {light green} {%T} {cyan} {yellow} {%5} {white} {%6} };
 
    #showme {\a\};
 
    #line log {$chat_log} {$ppose}
 
}
 
#act {^In a page-pose to you and %5, %6 %7} {
 
    #format {ppose} {%c%t %cP-P %c%s} {light green} {%T} {cyan} {white} {%0};
 
    #nop {#format {ppose} {%c%t %cP-P %c(%s) - %c%s %c%s} {light green} {%T} {cyan} {yellow} {%5} {light blue} {%6} {white} {%7} };
 
    #showme {\a\};
 
    #line log {$chat_log} {$ppose}
 
}
 
#act {^In a page-pose to %5 and you, %6 %7} {
 
    #format {ppose} {%c%t %cP-P %c%s} {light green} {%T} {cyan} {white} {%0};
 
    #showme {\a\};
 
    #line log {$chat_log} {$ppose}
 
}
 
#act {^%5 pages, %2 to you.} {
 
    #nop {#py fs.battle_trigger("%5", %2);};
 
    #format {page} {%c%t %c%5 %cpages,%c %c%s} {white} {%T} {yellow} {cyan} {yellow} {white} {%2};
 
    #showme {\a\};
 
    #line log {$chat_log} {$page}
 
}
 
#act {%5 pages, "%6" to you and %7.} {
 
    #format {page} {%c%t %c%s %cpages, %c"%s" to you and %s} {white} {%T} {yellow} {%5} {cyan} {white} {%6} {%7};
 
    #showme {\a\};
 
    #line log {$chat_log} {$page}
 
}
 
  
#nop {==================================================================}
+
When you _leave_ the main streets, the room-scale changes. For any combination of N, E, W, S, U, D that is NOT on the same 3D grid you need to insert a void room to tell the auto-mapper you're leaving that scale and thus also map.
#nop {sexytimes triggers}
 
#nop {==================================================================}
 
#act {^<Fuck> %5 wants to fuck you. Type fuck %5 to agree.} {
 
    #var sexy %5;
 
    #py fs.taken("$sexy", "pussy")
 
}
 
#act {<Fuck> %5 wants oral sex with you. Type fuck %5 mouth to agree.} {
 
    #var sexy %5;
 
    #py fs.taken("$sexy", "mouth")
 
}
 
#act {<Fuck> %5 wants an ass fuck with you. Type fuck %5 ass to agree.} {
 
    #var sexy %5;
 
    #py fs.taken("$sexy", "ass")
 
}
 
#act {^<OOC> %5 says, "!check"} {#py fs.secret("%5", "%5")}
 
  
#act {^<Submit> $playername submits to %5} {
+
Unfortunately due to the way TinTin++'s mapping works you have to do this 'in reverse' (after you've already left the grid).
    #py fs.fucked()
 
}
 
  
#nop {==================================================================}
+
DIRECTIONOUT is whichever way goes back the way you came. DIRECTIONIN is the direction you'd take FROM that other room to go back to where you're actually standing now.
#nop {misc game and partying triggers}
 
#nop {==================================================================}
 
#act {Type +reward now!} {+reward}
 
#act {(You sense a new message at OOC - %1 by Avatar: +REWARD)} {+reward}
 
#act {You receive a new mission. Clear out opposition at %5!} {#send ooc My daily is: %5; #var daily_status 1}
 
#act {^gothere %30} {#var place %30; #py fs.gothere("$place")}
 
#act {You have been invited to join %5's party. Type pjoin %5 to accept and meet them.} {#send pjoin %5}
 
#act {%5 (Level %6) wishes to join your party. Type pinvite %5 to accept.} {#py fs.party_invite("%5", int("$party_invite_status"))}
 
  
#nop {==================================================================}
+
#map insert DIRECTIONOUT void
#nop {battle and ooc game triggers}
+
#map goto VNUM (the void room ID that got created)
#nop {==================================================================}
+
#map roomflag hide
#act {<OOC> %5 says, "!target %4"} {#send ooc Targeting %4; #send target %4}
+
#map move DIRECTIONIN
#act {<OOC> %5 says, "$playername !%.aily %5"}{#send daily %5}
 
#act {<OOC> %5 says, "$playername !pinvite %6"} {#send pinvite %6}
 
#act {<OOC> %5 says, "$playername !go %4"} {#var loc %4; #py fs.gothere("$loc")}
 
#act {<OOC> %5 says, "$playername !auto"} {#send ooc Auto on.; #send auto on}
 
#act {<OOC> %5 says, "$playername !search"} {#send search}
 
#act {<OOC> %5 says, "$playername !bot on"} {#send botmode on}
 
#act {<OOC> %5 says, "$playername !bot off"} {
 
    #var boton 0;
 
    #if {$bat == 1} {
 
        once botmode off;
 
        #send AUTO: Understood. Botmode will be switched off when the current battle is completed.
 
    };
 
    #else {
 
        botmode off
 
    }
 
}
 
#act {<OOC> %5 says, "$playername !danger"} {#send +haz; #send ooc Danger: $danger Pheromone: $pheromone}
 
#act {<OOC> %5 says, "$playername !leave"} {#send pleave}
 
#act {<OOC> %5 says, "$playername !home"} {#send home}
 
#act {<OOC> %5 says, "$playername !help"} {
 
    #send @paste %5;
 
    #send For an automatic pinvite use ooc $playername !pinvite;
 
    #send To get me to leave the party use ooc $playername !leave;
 
    #send To get me to go home use ooc $playername !home;
 
    #send To get me to target an enemy use ooc !target X;
 
    #send To get me to switch auto combat on use ooc $playername !auto;
 
    #send To get me to search use ooc $playername !search;
 
    #send To get me to go somewhere use ooc $playername !go DIR where DIR is one of: [n,e,s,w,ne,nw,se,sw,u,d];
 
    #send Switch on botmode with ooc $playername !bot on;
 
    #send Switch off botmode with ooc $playername !bot off;
 
    #send You can verify my currently set danger levels with ooc $playername !danger;
 
    #send Refresh my daily with ooc !daily [daily location] at the correct location;
 
    #send .
 
}
 
  
 +
===Leaving the Grid (not a cardinal direction)===
  
#nop {==================================================================}
+
When entering a room that is NOT one of the cardinal directions you'll first need to 'dig' a room towards that path.
#nop {fighty time triggers}
 
#nop {==================================================================}
 
#act {DN%5 PH%6 %7} {#var danger %5; #var pheromone %6}
 
#act {^You have been defeated!} {
 
    #if {boton == 1} {
 
        #if {bat == 1} {
 
            once #send ooc $playername !bot off
 
        };
 
        #else {
 
            #send ooc $playername !bot off
 
        }
 
    }
 
    #var boton 0;
 
    #var bat 0;
 
    #showme {\a\}
 
}
 
#act {^<Botmode> Your bot mode is now enabled} {
 
    #format {bot_status} {%cBOT%c} {light red} {white};
 
    #var boton 1;
 
    #var bat 0;
 
    #send search
 
}
 
#act {^<Botmode> Your bot mode is now disabled} {
 
    #format {bot_status} {%cBOT%c} {light green} {white};
 
    #var boton 0;
 
    #var bat 0;
 
    #unticker searching
 
}
 
#act {^<Botmode> Botmode is currently on, type botmode off to disable it.} {
 
    #format {bot_status} {%cBOT%c} {light red} {white};
 
    #send +haz
 
}
 
#act {NO INFECTION DATA FOUND FOR THIS AREA.} {
 
    #showme {Safe Area, botmode being switched off};
 
    #if {$boton == 1} {
 
        #format {bot_status} {%cBOT%c} {light green} {white};
 
        #send {ooc $playername !bot off}
 
    }
 
}
 
#act {^<Search> $playername searches for trouble.} {#delay {search_notification} {#line log {$stats_log} {Time to search again!}} {60}}
 
#act {^<Random Encounter> $playername is ambushed!} {
 
    #showme AMBUSHED;
 
    #var ambushed 1;
 
    #showme BATTLE STARTED;
 
    #var bat 1;
 
    #delay {ambushed} {#var ambushed 0} {60};
 
    #py fs.battle_timer("started")
 
}
 
#act {^== ROUND %5 ==} {
 
    #var bat 1;
 
    #format {round} {%c== ROUND %5 ==} {white};
 
    #line log {$stats_log} {$round};
 
    #send look
 
}
 
#act {^</////// Battle %5! ///////>} {
 
    #var bat 0;
 
    #var ambushed 0;
 
    #py fs.config.feral_list = {};
 
    #send goic;
 
    #send $once_command; #var once_command nop;
 
    #py fs.config.feral_list = {};
 
    #if {$daily_status == 1} {#send {quest}};
 
    #line log {$stats_log} {Battle %5!};
 
    #py fs.battle_timer("finished");
 
    #send fprompt;
 
    #send mprompt
 
}
 
#act {^<%6/%7 hp %10 /%11 m 0 /0 mv} {
 
    #var hpnow %6; #var hpmax %7;
 
    #var ennow %10; #var enmax %11;
 
    #math {hpper} {($hpnow/$hpmax) * 100.00};
 
    #math {enper} {($ennow/$enmax) * 100.00};
 
    #if {$boton == 1} {
 
        #if {$bat == 0} {
 
            #nop {#showme {NOT IN BATTLE}};
 
            #if {$hpper >= $hptop && $enper >= $entop && $ambushed == 0} {
 
                #undelay {search_time};
 
                #delay {search_time} {#send search} {10}
 
            };
 
            #else {
 
                #undelay {waiting};
 
                #delay {waiting} {#send mprompt} {10}
 
            };
 
        };
 
        #else {
 
            #if {$hpper <= $submit} {
 
                #send submit $targeting
 
            };
 
        };
 
    };
 
    #format hitpoints {%cHP %d/%d%c} {light green} {$hpnow} {$hpmax} {white};
 
    #format energypoints {%cEN %d/%d%c} {light blue} {$ennow} {$enmax} {white};
 
    #if {$hpper < $MAX || $enper < $MAX} {
 
        #showme STATUS: $hitpoints $energypoints $hpper $enper
 
    };
 
    time
 
}
 
#act {^Health: %5 -> %6/%7} {
 
    #var hpnow %5; #var hpmax %6;
 
    #format hitpoints {%cHP %d/%d%c} {light green} {$hpnow} {$hpmax} {white};
 
    #send mprompt;
 
    #showme HEALTH
 
}
 
#act {^Energy: %5 -> %6/%7 --} {
 
    #var ennow %5; #var enmax %6;
 
    #format energypoints {%cEN %d/%d%c} {light blue} {$ennow} {$enmax} {white};
 
    #send mprompt;
 
    #showme ENERGY
 
}
 
#act {Players: %6} {
 
    #var player_list %6;
 
    #py fs.create_player_list("$player_list");
 
    #send fprompt;
 
    #send mprompt
 
}
 
#act {^Things: %6} {
 
    #var ferals %6;
 
    #py fs.create_feral_list("$ferals");
 
    #py fs.target_feral(int($target_mode))
 
}
 
#act {^<Summon> Pet: %5 appears at %6. %7 side.} {
 
    #showme PET SUMMONED, RETARGETING;
 
    #var targeting %6;
 
    #send Target $targeting;
 
    #py fs.config.current_target = int($targeting);
 
    #py fs.target_feral(int($target_mode))
 
}
 
#act {<Daily Mission> You make progress towards the daily mission(%5).} {
 
    #var completion %5;
 
    #if {$messages == 1} {
 
        #send ooc Mission completion: $completion;
 
        #send quest
 
    };
 
    #if {$beeps == 1} {
 
        #showme {\a\}
 
    }
 
}
 
#act {^Your daily mission is in %5: [    %6  ]} {
 
    #var reward_lvl %6;
 
    #line log {$stats_log} {Reward lvl: $reward_lvl};
 
    #if {$daily_reward == "400%"} {
 
        #send ooc AUTO: 400% reached! Activate speeeeeedmode!;
 
        #var daily_status 0
 
    }
 
}
 
#act {<Daily Mission> You have earned a difficulty ratio of %5, this earns you %6 reward tokens!} {
 
    #line log {$stats_log} {Got %6 tokens from daily mission!};
 
    #var completion;
 
    #var reward_lvl
 
}
 
#act {<Daily Mission> You have completed the mission!} {
 
    #delay {daily_refresh} {#send ooc AUTO: Time to get a new daily!} {64800};
 
    #var daily_status 0;
 
    #var daily_complete $clock;
 
    #py fs.daily_done()
 
}
 
#act {XP Gained: %5  --  Freecred Gained: %6} {
 
    #format {win} {%cXP: %5 FC: %6} {yellow};
 
    #line log {$stats_log} {$win}
 
}
 
#act {^Somewhere on the muck, %5 has disconnected.} {#nop} {5}
 
#act {^%5 has disconnected.} {
 
    #var boton 0;
 
    #format {disconnected} {%c%t %c%5 disconnected!%c} {light green} {$clock} {light red} {white};
 
    #line log {$chat_log} {$disconnected};
 
    #if {$bat == 1 && $boton == 1} {
 
        once botmode off;
 
        #send {ooc AUTO: %5 has dropped, botmode will be automatically switched off at the end of the battle.}
 
    }
 
} {6}
 
#act {^<Salvage> $playername manages to salvage %5 %6 salvage!} {
 
    #format {sal} {%cSalvage: %5 %6} {red};
 
    #line log {$stats_log} {$sal}
 
}
 
#act {^What a find! $playername's keen grasp of the scholarly leads them to find a %5! It's worth %6 Freecreds!} {#line log {$stats_log} {Item: %6FC}}
 
  
#nop {==================================================================}
+
#map dig OFFGRID new (if you've never been there before, new is optional)
#nop {Dump battle damage to stats log.}
+
#map dig OFFGRID VNUM (the number of the room you're going back to)
#nop {==================================================================}
+
 +
#map info (will list info about the current room, including 'exits' that lead here)
 +
 +
An example, Zypher's Front Door and Main Lobby.
  
#act {^<[%5%]$playername><%6> %7 is cured of %8!} {#format {effect} {%c%6 %c%8%c} {magenta} {green} {white}; #line log {$stats_log} {$effect}}
+
#map dig o
#act {^<[%5%]$playername><%6> %7 affects $playername!} {#format {effect} {%c%6%c} {magenta} {green} {white}; #line log {$stats_log} {$effect}}
+
o
#act {^<[%5%]$playername><%6> %7. %8 takes %9 damage!} {#format {effect} {%c%6 %c%9%c} {magenta} {red} {white}; #line log {$stats_log} {$effect}}
+
#map info
#act {^<[%5%]$playername><%6> %7. %8 takes %9 damage! (%10)} {#format {effect} {%c%6 %c%9%c} {magenta} {red} {white}; #line log {$stats_log} {$effect}}
+
#map dig z ROOMID
#act {^<[%5%]$playername><%6> %8 takes %9 damage! (%10)} {#format {effect} {%c%6 %c%9%c} {magenta} {red} {white}; #line log {$stats_log} {$effect}}
 
#act {^<[%5%]$playername><%6> Pet: %7 %8(%9) takes %10 damage!} {#format {effect} {%c%6 %c%10%c} {magenta} {red} {white}; #line log {$stats_log} {$effect}}
 
#act {^<[%4]%5($playername)><%6> %7. %8 takes %9 damage!} {#format {effect} {%c%5 %c%6 %c%9%c} {magenta} {green} {red} {white}; #line log {$stats_log} {$effect}}
 
#act {^<%5($playername)><%6> %7 is healed for %8 damage!} {#format {effect} {%c%5 %c%6 %8%c} {magenta} {green} {white}; #line log {$stats_log} {$effect}}
 
#act {^<$playername><%5> %6 is healed for %7 damage!} {#format {effect} {%c%5 %c%7 %c} {magenta} {light green} {white}; #line log {$stats_log} {$effect}}
 
#act {^<[%5%]$playername><%6> $playername is led astray by %8. %9 and loses momentum.} {#nop}
 
#act {^<Vampiric> $playername draws %5 points of regeneration.} #format {effect} {%cVampiric %c%5 %c} {magenta} {light green} {white}; #line log {$stats_log} {$effect}
 
  
#nop {==================================================================}
+
You get the ROOMID from the Entrance list in the info dump.
#nop {grab player stats from fprompt for logging.}
 
#nop {==================================================================}
 
  
#function {stats_formatting} {
+
===Searching for what you want===
    #nop {input vars: name;hp_now;hp_max;en_now;en_max;name_colour;list_name};
 
    #math {hp_per} {(%2/%3) * 100.00};
 
    #math {en_per} {(%4/%5) * 100.00};
 
    #var hp_col @colourise{$hp_per};
 
    #var en_col @colourise{$en_per};
 
    #format stats {%s%.22s %s%s/%s %s%s/%s} {%6} {%1} {$hp_col} {%2} {%3} {$en_col} {%4} {%5};
 
    #list {%7} {add} {$stats}
 
}
 
  
 +
While you might think that '''#map find''' is what you want, it isn't.  This finds a route to someplace you might be thinking of.  If you want a list of results, that's what '''#map list''' is for.  (I know, someone years ago should have called 'find' 'route' instead.)
  
#function {colourise} {
+
This can be rather useful for getting a list of rooms in an area.  Like Zypher's building.  Which has both an elevator and a stairwell.
    #if {%0 >= 100} {
 
        #nop {bold cyan};
 
        #return <168>
 
    };
 
    #elseif {%0 > 70} {
 
        #nop {bold blue};
 
        #return <148>
 
    };
 
    #elseif {%0 > 50} {
 
        #nop {bold green};
 
        #return <128>
 
    };
 
    #elseif {%0 > 30} {
 
        #nop {bright yellow};
 
        #return <138>
 
    };
 
    #elseif {%0 > 20} {
 
        #nop {bold white};
 
        #return <178>
 
    };
 
    #elseif {%0 > 10} {
 
        #nop {bright red};
 
        #return <118>
 
    };
 
    #else {
 
        #nop {blink red};
 
        #return <018>
 
    }
 
}
 
  
 +
Go up/down the stack in Zephyr if you haven't already.  Since my gohome takes me to Triage, Basement 1 I like to '''dig''' E1 from there first.
  
#nop {Feral pets}
+
#map list {roomarea} {Zephyr Inc}
#act {^Pet: %6%s<%7/%8 hp - %9/%10 en>} {
+
#map dig e1
    #format feral_pet_name {Pet: %s} {%6};
+
e1
    #var stats @stats_formatting{$feral_pet_name;%7;%8;%9;%10;<218>;fpet}
 
} {5}
 
#nop {Numbered Ferals}
 
#act {%5. %6%s<%7/%8 hp - %9/%10 en>} {
 
    #format feral_name {%s. %s} {%5} {%6};
 
    #var stats @stats_formatting{$feral_name;%7;%8;%9;%10;<118>;fer}
 
}
 
#nop {Player pets}
 
#act {%5(%6)%s<%7/%8 hp - %9/%10 en>} {
 
    #format pet_name {%s(%s)} {%5} {%6};
 
    #var stats @stats_formatting{$pet_name;%7;%8;%9;%10;<138>;pet}
 
} {6}
 
#nop {Primes - they don't have a number so add one.}
 
#act {Prime %6%s<%7/%8 hp - %9/%10 en>} {
 
    #var stats @stats_formatting{%6;%7;%8;%9;%10;<118>;fer}
 
} {5}
 
#nop {Players}
 
#act {%6%s<%7/%8 hp - %9/%10 en>} {
 
    #var stats @stats_formatting{%6;%7;%8;%9;%10;<128>;ply}
 
} {6}
 
#act {^<ENDLIST>} {
 
    #foreach {$fpet[%*]} {temp} {#list {stats_list} {add} {$temp}};
 
    #foreach {$fer[%*]} {temp} {#list {stats_list} {add} {$temp}};
 
    #foreach {$pet[%*]} {temp} {#list {stats_list} {add} {$temp}};
 
    #foreach {$ply[%*]} {temp} {#list {stats_list} {add} {$temp}};
 
  
    #list {fpet} {size} {fpet_list_size};
+
I'm going to use the names in this example in place of VNUMs, please use the VNUM instead.
    #list {fer} {size} {fer_list_size};
 
    #list {pet} {size} {pet_list_size};
 
    #list {ply} {size} {ply_list_size};
 
    #format summary {<878>[P:%d p:%d F:%d f:%d] <168>0<148>98<128>76<138>54<178>3<118>2<018>1} {$ply_list_size} {$pet_list_size} {$fer_list_size} {$fpet_list_size};
 
    #list {stats_list} {add} {$summary};
 
   
 
    #math {line_buffer_size} {30 - $stats_list_size};
 
    #loop 1 $line_buffer_size blah {#list {stats_list} {insert} {1} { }};
 
    #foreach {$stats_list[%*]} {temp} {#line log {$hp_log} {$temp}};
 
    #list {fpet} {clear};
 
    #list {fer} {clear};
 
    #list {pet} {clear};
 
    #list {ply} {clear};
 
    #list {stats_list} {clear};
 
    #sys echo " " > '$hp_log'
 
}
 
  
#nop {==================================================================}
+
Inside of E1
#nop {Auto targeting}
+
#map dig b1 Living Quarters & Child Care, Basement 1
#nop {==================================================================}
+
#map dig f1 Main Lobby, Floor 1
#act {^<Power> %3. %4 is defeated by %5!} {
+
#map dig f2 Main Lobby, Floor 2
    #nop {FERAL};
+
#map dig f3 Main Lobby, Floor 3
    #var tgt_no %3; #var tgt_name %4;
+
#map dig f4 Circular Hallway, Floor 4
    #format {pwr} {%c%3 %c(%5)%c} {red} {cyan} {white};
+
#map dig b2 Dormitories
    #line log {$stats_log} {$pwr};
+
#map dig b3 new
    #send look;
+
#map info
    #py fs.target_feral(int($target_mode))
 
} {5}
 
#act {^<Power> %4(%5) is defeated by %6!} {
 
    #nop {PET};
 
    #var tgt_name %4;
 
    #format {pwr} {%c%4 Pet down!} {cyan};
 
    #line log {$stats_log} {$pwr}
 
} {6}
 
#act {^<Power> Prime %4 is defeated by %5!} {
 
    #var tgt_name %4;
 
    #format {pwr} {%c%4 has fallen! (%5)} {light red};
 
    #line log {$stats_log} {$pwr};
 
    #py fs.player_down_check("$tgt_name")
 
}
 
#act {^<Power> %4 is defeated by %5!} {
 
    #nop {PLAYER};
 
    #var tgt_name %4;
 
    #format {pwr} {%c%4 has fallen!} {light red};
 
    #line log {$stats_log} {$pwr};
 
    #py fs.player_down_check("$tgt_name")
 
} {8}
 
  
#nop {==================================================================}
+
I hadn't been to b3 yet, '''new''' is optional, but clarifies the directions.  The NEXT info command shows there isn't an exit back to e1 yet.  The dig command will be used to create one (use the VNUM from the info run while above, inside of E1).
#nop {Status stuff}
 
#nop {==================================================================}
 
#act {--Contents of %6--} {
 
    #format current_location {%c%s} {white} {%6}
 
} {1}
 
  
#gag {^[Tick] %5}
+
b3
#act {^[Tick] Avatar **Global Tick - %5 load**} {
+
#map info
    #format load {%cLD %s%c} {red} {%5} {white}
+
#map dig o VNUM
}
+
o
 +
b2
 +
#map dig e1 VNUM
 +
e1
 +
f1
 +
#map dig e1 VNUM
 +
e1
 +
f2
 +
#map dig e1 VNUM
 +
  e1
 +
f3
 +
#map dig e1 VNUM
 +
e1
 +
f4
 +
#map dig e1 VNUM
 +
 +
===Delete / Remove an Exit (unlink)===
  
 +
The command isn't any of the words you'd probably think to look for; Not Delete (which kills a room, by VNUM or relative exit towards), Nor is it Remove (not a command), it's Unlink.
  
#function {bar_formatting} {
+
#map unlink ExitDirection
    #math {hp_per} {(%2/%3) * 100.00};
 
    #math {en_per} {(%4/%5) * 100.00};
 
    #var hp_col @colourise{$hp_per};
 
    #var en_col @colourise{$en_per};
 
    #format bar_stats {%c%s %s%s/%s %s%s/%s} {%6} {%1} {$hp_col} {%2} {%3} {$en_col} {%4} {%5};
 
    #return $bar_stats
 
}
 
  
#prompt {^<%6/%7 hp %10 /%11 m 0 /0 mv} {<100>@bar_formatting{$playername:;%6;%7;%10;%11;light magenta}<178> - $current_location - $load - $party_invite_status_text - $bot_status - $clock $completion $reward_lvl}
+
===Global Teleports===
#send mprompt
 
#send +haz
 
daily?
 
</pre>
 
  
 +
A special, in this case for normal players fake, room is needed for holding a list of 'exits' that work globally (anywhere).
  
Main python script:<br>
+
In my example beneath, I went to my respawn point and decided to call 'GLOBAL', an exit that doesn't exist, the way to the global room.
<pre>
 
#! /usr/bin/env python
 
  
# Remember to double braces - they're used in string comprehension!
+
Note your current '''Room vnum:''' room number to return to, this will be called VNUMBACK later.  Then move the context of the map system to the newly created GLOBAL room.
import datetime
 
import cPickle as pickle
 
import random
 
import re
 
import time
 
  
import config
+
#map info
 +
#map dig GLOBAL
 +
#map goto VNUM(from dig's new room)
 +
#map set {roomarea} {GLOBAL}
 +
#map set {roomname} {GLOBAL}
 +
#map info
 +
 +
You can use #map list {roomarea} {GLOBAL} to find this room's vnum again, these don't all have to be added at the same time; you can return later to add new 'global exists' as you find more one line commands that uniquely take you to an exact room.  This is less helpful for, E.G. scripted NPC movements (like going to bed with an NPC in their room, or to another city).
  
 +
#map list {roomarea} {GLOBAL}
 +
#map goto VNUM
 +
#map dig gohome VNUM
 +
#map dig home VNUM
 +
#map dig respawn VNUM
 +
#map dig upgrade dojo VNUM
 +
#map dig tport ...
 +
...
 +
#map goto VNUMBACK (that VNUM of where you actually are on the MUCK / MUD)
  
ses = "fs"
+
===Eureka===
config.time_check = time.clock()
 
  
 +
Eureka is recent enough to have an OK coverage on the [https://flexiblesurvival.com/mapping official map].  However as you can see from glancing at it, there will need to be a judicious use of void (but not hidden) rooms to make the scaling match.
  
def read_vars():
+
#map insert (directionback) void
    filename = "/home/anwen/tt/fs_vars_{plyr}".format(plyr=config.player.lower())
 
    #print(filename)
 
    f = open(filename, "rb")
 
    config.party_list, config.next_daily = pickle.load(f)
 
    f.close()
 
  
 +
The actual bad part comes with inserting void rooms to join spans.
  
# write variables to a pickle
+
#map leave
def write_vars():
+
#map return
    filename = "/home/anwen/tt/fs_vars_{0}".format(config.player.lower())
+
(Note the room number)
    try:
+
... Go around the gap
        f = open(filename, "wb")
+
#map dig ((direction of gap)) ((actual room number))
        pickle.dump([config.party_list, config.next_daily], f)
+
#map insert ((direction of gap)) void
        f.close()
+
(Note the ***void***room number)
        #print("Data successfully written.")
+
((direction of gap; this moves you to the side that's missing a - (has a space instead) ))
    except:
+
#map dig ((Direction towards that new voidroom)) ((voidroom number))
        #print("Some data not found.")
 
        f = open(filename, "wb")
 
        config.party_list = []
 
        config.last_daily = 0
 
        pickle.dump([config.party_list, config.next_daily], f)
 
        f.close()
 
       
 
  
# =============================================================================
+
===Off the Map===
# Battle related functions
 
# =============================================================================
 
def create_feral_list(ferals):
 
    '''Compile a dict of ferals based on the contents of the "Things" list.
 
    Format is {feral_number: feral_name}
 
    '''
 
    config.feral_list = {}
 
    temp_list = ferals.split(",")
 
    for item in temp_list:
 
        item = item.strip()
 
        if "Pet:" in item:
 
            # Ignore player pets, NPCs or enemy pets.
 
            if config.debug: print("pet: {0}".format(item))
 
            continue
 
        elif "(!)" in item:
 
            # Game NPC's and items
 
            if config.debug: print("player pet: {0}".format(item))
 
            continue
 
        elif ". " in item:
 
            # It's a feral, add it to the dict.
 
            if config.debug: print("Feral! {0}".format(item))
 
            ind, name = item.split(". ")  # format is 1. name
 
            config.feral_list[int(ind)] = name
 
        elif "Prime" in item:
 
            #special cases for prime fights.
 
            name = item[6:]
 
            config.feral_list["Prime"] = name
 
        elif "backup" in item:
 
            name, ind = item.split(" backup ")
 
            config.feral_list[int(ind)] = name
 
        else:
 
            print("not sure about this one")
 
            print(item)
 
            continue
 
    print(config.feral_list)
 
  
       
+
This pair of commands allows you to stop adding to the map automatically, and then toggle it back on when you've returned to the same room you started the command inside of.
def target_feral(target_mode):
 
    '''Select a target from the Things list. target_mode 0 broadcasts to all
 
    players who react to ooc targeting messages. Target mode 1 is player only.
 
    '''
 
    # Check that there's something in the list:
 
    if len(config.feral_list) == 0:
 
        #print("Nothing in feral_list")
 
        return None
 
    # Check if the target feral is still there:
 
    if config.current_target in config.feral_list.keys():
 
        #print("Still targeting feral: {0} {1}".format(config.current_target, config.feral_list[config.current_target]))
 
        return None
 
    # Determine if the feral is a priority target based on its name:
 
    for ii in config.feral_list.keys():
 
        for item in config.feral_priority:
 
            if item in config.feral_list[ii]:
 
                config.current_target = ii
 
                #print("targeting {0} {1}".format(ii, config.feral_list[ii]))
 
                if target_mode == 1:
 
                    command = ["#send ooc !target {blarg}.".format(blarg=ii)]
 
                else:
 
                    command = ["#send target {blarg}. ".format(blarg=ii)]
 
                output(ses, command)
 
                return
 
    # Otherwise just target the lowest numbered feral:
 
    feral = config.feral_list.keys()[0]
 
    if feral != config.current_target:
 
        config.current_target = feral
 
        if target_mode == 1:
 
            command = ["#send ooc !target {blarg}.".format(blarg=feral)]
 
        else:
 
            command = ["#send target {blarg}. ".format(blarg=feral)]
 
        output(ses, command)
 
  
 +
#map leave
 +
#map return
 +
OR
 +
#map goto VNUM (useful if you gohome / etc)
  
def create_player_list(players):
+
As another reminder, you can also search for the room area (between the [ ] brackets) to get a room VNUM to return to if you've gone to a different spot.
    temp_list = players.split(",")
 
    config.player_list = []
 
    for item in temp_list:
 
        item = item.split("] ")[1]
 
        item = item.strip()
 
        if "[" in item:
 
            item = item.split("[")[0]
 
        # Ignore "me" and add other players to list
 
        if not re.match(config.player, item):
 
            config.player_list.append(item)
 
    config.player_list.append(config.player)
 
    #print(config.player_list)
 
  
 +
#map list {roomarea} {AREA NAME}
  
   
+
==Windows 10 WSL==
def battle_timer(status):
 
    if status == "started":
 
        config.battle_started = datetime.datetime.now()
 
    else:
 
        a = datetime.datetime.now()
 
        try:
 
            diff = a - config.battle_started
 
        except NameError:
 
            #print("Doesn't look like the battle started!")
 
            return
 
        command = ["#showme Battle Duration: {{{0}}}".format(diff)]
 
        #print("TEST #line log {{stats_{0}}} {1}".format(config.player.lower(), diff))
 
        output(ses, command)
 
   
 
  
# =============================================================================
+
If you'd like to use TinTin++ on Windows, installing a [https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux WSL] based distribution such as Debian is highly recommended. If you have experience with a different distribution maintaining a server, including getting perl5 dependencies installed based on error messages, then go ahead and go with that. Debian has been selected here for being focused on Freedom, Liberty, and Stability, as well as generally providing clear directions for upgrades.
# misc stuff
 
# =============================================================================
 
def daily_done():
 
    '''saves time of completion of last daily'''
 
    config.next_daily = datetime.datetime.now() + datetime.timedelta(hours=18)
 
    write_vars()
 
   
 
   
 
def daily_timer():
 
    #print(config.next_daily)
 
    if config.next_daily == 0:
 
        command = ["#showme No daily data saved!"]
 
        output(ses, command)
 
        return
 
    time_diff = config.next_daily - datetime.datetime.now()
 
    hours, rem = divmod(time_diff.total_seconds(), 3600)
 
    mins, secs = divmod(rem, 60)
 
    #print("Time check: {0}:{1}:{2}".format(hours, mins, secs))
 
    if time_diff.total_seconds() <= 0:
 
        command = ["#var next_daily Ready to get next daily mission.", "#showme Go get your daily!"]
 
    else:
 
        a = "{0}h {1}m {2}s left until daily available.".format(hours, mins, secs)
 
        command = ["#var next_daily {0}".format(a), "#showme {0}".format(a)]
 
    output(ses, command)
 
  
 +
Note: Windows 10 S tries to be a "safe" OS that [https://devblogs.microsoft.com/commandline/will-linux-distros-run-on-windows-10-s/ restricts user freedom, and thus won't run even WSL].  They also offer an upgrade, for a fee, to normal-freedom Windows 10 (no S).
  
def edit_descrip(location):
+
Please follow Debian's [https://wiki.debian.org/InstallingDebianOn/Microsoft/Windows/SubsystemForLinux official guide] for installing on WSL. It will likely have links updated as versions of things change.
    msgs = config.descriptions[config.player.lower()]
 
  
    #delete current description and write new descrip:
+
==History==
    if "home" in location:
 
        command = msgs["home"]
 
    elif "away" in location:
 
        command = msgs["away"]
 
    output(ses, command)
 
  
 +
[https://en.wikipedia.org/wiki/TinTin%2B%2B TinTin++] is the result of almost 30 years of development by a series of programmers.  While an absolute dinosaur of a computer from the era of it's origin in the mid 1990s probably couldn't run it today, just about anything from the last 20 years probably can.
  
def gothere(location):
+
Several older versions of this wiki page exist (updated during 2013 and 2015) with the final version of that series updated on [https://wiki.flexiblesurvival.com/index.php?title=Tintin&oldid=310992 2015-08-25T14:09:09] you might find Anden's examples useful if interested in battle tactics, though they're written assuming python2 (which is no longer supported); while Inutt's code repository uses some perl5 helpers that could be replaced or omitted entirely without breaking the basic multipane functionality. (They help produce the formatted status window and optionally near realtime weather.)
    if location.lower() in ["n","e","s","w","ne","nw","se","sw","u","d"]:
 
        command = ["#send {loc}".format(loc=location)]
 
    elif location in config.places:
 
        command = config.places[location]
 
    else:
 
        command = ["#showme I don't know how to get there!"]
 
    output(ses, command)
 
  
 
+
Inutt, one of the coding wizards for FS, has a nice setup for TinTin that's published on their [https://gitlab.com/inutt/flexible-survival-ui GitLab] page.  Ketsueki made some small contributions (currently a pull request) to make it more suitable for users with different terminal sizes. Though it does expect a computer screen capable of displaying at least 120 columns of characters across (in that case a split of about 29 for the map and status area, and thus 90 or so for the main text area is recommended). If you're on a 1080p+ monitor, using a normal Graphical User Interface (E.G. Windows, OS X, anything else that runs a web browser) and using a full-screen "terminal" program of any sort (find one you like for your OS) you probably do have that many columns.
def party_invite(person, status):
 
    if status == 2:  # open to invites
 
        command = ["#send pinvite {per}".format(per=person)]
 
    elif status == 0:  # closed to invites
 
        command = ["#send page {per}=AUTOMSG: Sorry, not inviting at present".format(per=person)]
 
    elif status == 1: # invite if trusted
 
        if person in config.party_list:
 
            command = ["#send pinvite {per}".format(per=person)]
 
        else:
 
            command = ["#send page {per}=AUTOMSG: Sorry, you are not in my autoinvite list, please wait a few moments.".format(per=person)]
 
    output(ses, command)
 
 
 
 
 
def party_list(task, person):
 
    a = str(config.party_list)
 
    if task == "add":
 
        if person not in config.party_list:
 
            config.party_list.append(person)
 
            command = ["#showme {per} added to party list!".format(per=person)]
 
        else:
 
            command = ["#showme {per} already in the party list! {check}".format(per=person, check=a)]
 
    elif task == "del" and person in config.party_list:
 
        try:
 
            config.party_list.remove(person)
 
            command = ["#showme {per} removed from party list!".format(per=person),
 
                      "#showme {check}".format(check=str(config.party_list))
 
                      ]
 
        except:
 
            command = ["#showme Couldn't find this person. {check}".format(check=a)]
 
    elif task == "check":
 
        command = ["#showme {check}".format(check=a)]
 
    else:
 
        command = ["#showme What? {task}".format(task=task)]
 
    write_vars()
 
    output(ses, command)
 
 
 
 
 
# =============================================================================
 
# Output to tintin
 
# =============================================================================
 
 
 
# Take the input commands and pass them to tt++
 
def output(ses, command):
 
    for item in command:
 
        #print("#py tintin ({ses}) #showme Sending: {cmd}".format(ses=ses, cmd=item))
 
        print("#py tintin ({ses}) {cmd}".format(ses=ses, cmd=item))
 
</pre>
 
 
 
config script:<br>
 
</pre>
 
#! /usr/bin/env python
 
global __DBNAME__
 
 
 
debug = False
 
hp = 90
 
en = 90
 
submit = 10
 
next_daily = 0
 
feral_list = {}
 
current_target = 0
 
player_list = []
 
player_stats = []
 
pets_stats = []
 
feral_stats = []
 
feral_pets_stats = []
 
feral_priority = ['Prime', 'Goo Girl', 'Perplexing', 'Front and Center', 'Regenerator']
 
 
 
places = {"Ice_Caves" : ["#20 e", "#2 s", "#2 e", "#1 s", "#3 e", "#1 n",
 
                        "#5 e", "n", "e", "n", "e", "n", "e", "n", "e",
 
                        "n", "e", "n", "e", "n", "1", "e", "e", "#3 n",
 
                        "e", "b", "n", "d"],
 
 
 
          "Scrublands" : ["#20 e", "s", "#2 e", "s", "#3 e", "n",
 
                          "#5 e", "n", "e", "n", "e", "n", "e", "n", "e",
 
                          "n", "e", "n", "e", "n", "1", "s", "sw", "e", "s",
 
                          "s", "s", "w", "w", "s"],
 
 
 
          "Peaks" : ["w","n","n","n","e","n","n","w"]
 
        }
 
 
 
 
 
anwen_msg = {"home" : ["#send editplayer", "2",
 
                  ".del 1", ".del 1", ".del 1", ".del 1",
 
                  ".del 1", ".del 1", ".del 1", ".del 1",
 
                  "A small ornate bell hangs from a ribbon attached above Anwen's tailbone. It emits an etherial light.",
 
                  "On close inspection it has the word 'Yes' scribed in it in flowing letters.",
 
                  "[if [player] is [looker]][line break][player]'s current mutation-strains are:[line break]head: [stat Mutation/head of [player]][line break]torso: [stat Mutation/torso of [player]][line break]arms: [stat Mutation/arms of [player]][line break]legs: [stat Mutation/legs of [player]][line break]skin: [stat Mutation/skin of [player]][line break]ass: [stat Mutation/ass of [player]][line break]groin: [stat Mutation/cock of [player]][end if]",
 
                  ",end", "q"
 
                ],
 
            "away" : ["#send editplayer", "2",
 
                  ".del 1", ".del 1", ".del 1", ".del 1",
 
                  ".del 1", ".del 1", ".del 1", ".del 1",
 
                  "Anwen wears a small, black, tight-fitting shirt adorned with The Red Court logo. A pair of tight shorts serve to cover Anwen's modesty.",
 
                  "A small ornate bell hangs from a ribbon attached above Anwen's tailbone. It emits an etherial light. On close inspection it has the word 'yes' scribed in it in flowing letters.",
 
                  "Whatever might that mean?",
 
                  "[if [player] is [looker]][line break][player]'s current mutation-strains are:[line break]head: [stat Mutation/head of [player]][line break]torso: [stat Mutation/torso of [player]][line break]arms: [stat Mutation/arms of [player]][line break]legs: [stat Mutation/legs of [player]][line break]skin: [stat Mutation/skin of [player]][line break]ass: [stat Mutation/ass of [player]][line break]groin: [stat Mutation/cock of [player]][end if]",
 
                  ",end", "q"]
 
        }
 
</pre>
 
 
 
 
 
 
 
I hope this is useful and wish you all the best.
 
Your friend, Anwen :3
 

Latest revision as of 08:33, 11 May 2020

Install

Required packages (for Debian Linux)

Become root, either via sudo -i or some other method.

Run all of these commands as root (yes, bash is probably pre-installed, this is just making sure).

apt-get update
apt-get install tmux tintin++ bash git nano \
libcommon-sense-perl \
libdatetime-perl \
libdatetime-event-sunrise-perl \
libfile-changenotify-perl \
libfile-pid-perl \
libjson-perl \
libtest-lwp-useragent-perl \
libpath-class-perl \
libtime-duration-perl

Type exit (or ctrl+d) to end the session if you opened a new root session for this.

FIXME: Are Getopt::Long and Term::ANSIColor built in, there don't seem to be Debian packages.

Download the script package

Next, run these commands as your normal user account (not root). Replace any bold names with file-paths of your choosing. Also remember that once a path exists, tab-complete often works for typing it out.

cd AnywhereYouWant
git clone https://gitlab.com/inutt/flexible-survival-ui.git fs-ui
chmod +x fs-ui/flexible_survival
fs-ui/flexible_survival

Very minimal config.json

nano fs-ui/config.json

Above nano is used to edit the config file, if you prefer a different editor use that. The launcher script should have setup the config file to be readable only by admins (root) and your exact user account.

  • "character": "", and
  • "password": "", MUST be filled out.

JSON ignores non-printing 'whitespace' and requires that a key/value sets (contained in { } and called an object) have values in the middle separated by commas, but NO comma at the end. {"character": "USER", "password": "PASSWORD"}. Any values you don't want to change can be deleted. Thus this might be an example minimal configuration file:

{
    "fs": {
       "character": "YOURUSER",
       "password": "YOURPASS"
    }
}

In the above the extra commas after the password line, and that closing } after the "fs" key have been removed. Again, the whitespace doesn't matter, so it could also be just (with no spaces even).

{"fs":{"character":"YOURUSER","password":"YOURPASS"}}

Setting where the window splits to panes

The numbers in the tmux section are how you configure the split of the window. You'll probably want to keep them and change them to suit your own needs.

    14 chat
 ------+-----------
 status|
 ------|   main
 map 27|
 64=>  |
   "chat_height": 14,
   "map_width": 64,
   "map_height": 27,
   "status_height": 16

Note: status_height doesn't actually set the height. It tells the status update script how tall this section is so that it might omit some spacing or features to fit.

Final config.json

You probably also want to set "data_dir": "SOMETHING", which is where your map data and log files will be stored. This won't be created for you, so make sure you create it first. E.G.

mkdir "~/Documents/Flexible Survival Logs"

Then your config.json file might look something like this

nano fs-ui/config.json
{
    "tmux": {
        "session_name": "Flexible Survival",
        "chat_height": 14,
        "map_width": 64,
        "map_height": 27,
        "status_height": 16
    },
    "fs": {
        "character": "YOURUSER",
        "password": "YOURPASS",
        "data_dir": "~/Documents/Flexible Survival Logs"
    }
}

If you want near-realtime weather, please sign up for An OpenWeatherMap account and make sure you retain and fill out the api_key with your account key. (NOT a login/password, but an account token that looks a bit like HEX00341AFEAAA0358 but longer, and probably actually all hex.)

If you're using WSL this gets more complicated. "/mnt/c/Users/WindowsAccountName/My Documents/Flexible Survival Logs" might work for most. If it doesn't, talk to whomever maintains the PC. Tab-completion is really helpful here... mkdir /mnt/c/User (TAB) and then hit tab a couple more times to get a good guess at user account and My Documents locations to start in.

With that setup you SHOULD be able to (after setup!)

Get updates

cd fs-ui
git pull

This can be run any time (in where-ever you had the download stored). If there are any updates or bugfixes; though you might need to update your config.json file to use new features.

Connect

  • fs-ui/flexible_survival

When running Ctrl+B (arrow key) (hold down control, tap b, release both; then press an arrow key) can be used to move the input-focus between panes (text areas within that window). Up and Down arrow keys are the ones you'll care about. Up to go to the chat channels, down to go back to muck input. You type all commands in the main muck window, even though the text shows up above.

Automapping

Inutt automated a lot of the mapping stuff, the 80% of it that's easy and not dependent on foreknowledge of inconsistencies.

On perfectly grid like streets, where all of the rooms are pixels on a grid, it works very well.

Undo

If you tried to go a given direction, and it wasn't a valid direction, you MAY need to undo. Inutt's main.tin script catches some, but not all, of these error messages. Undo will move you back on the map and delete the most recently created room.

#map undo

Leaving the Grid (in a grid direction)

When you _leave_ the main streets, the room-scale changes. For any combination of N, E, W, S, U, D that is NOT on the same 3D grid you need to insert a void room to tell the auto-mapper you're leaving that scale and thus also map.

Unfortunately due to the way TinTin++'s mapping works you have to do this 'in reverse' (after you've already left the grid).

DIRECTIONOUT is whichever way goes back the way you came. DIRECTIONIN is the direction you'd take FROM that other room to go back to where you're actually standing now.

#map insert DIRECTIONOUT void
#map goto VNUM (the void room ID that got created)
#map roomflag hide
#map move DIRECTIONIN

Leaving the Grid (not a cardinal direction)

When entering a room that is NOT one of the cardinal directions you'll first need to 'dig' a room towards that path.

#map dig OFFGRID new (if you've never been there before, new is optional)
#map dig OFFGRID VNUM (the number of the room you're going back to)

#map info (will list info about the current room, including 'exits' that lead here)

An example, Zypher's Front Door and Main Lobby.

#map dig o
o
#map info
#map dig z ROOMID

You get the ROOMID from the Entrance list in the info dump.

Searching for what you want

While you might think that #map find is what you want, it isn't. This finds a route to someplace you might be thinking of. If you want a list of results, that's what #map list is for. (I know, someone years ago should have called 'find' 'route' instead.)

This can be rather useful for getting a list of rooms in an area. Like Zypher's building. Which has both an elevator and a stairwell.

Go up/down the stack in Zephyr if you haven't already. Since my gohome takes me to Triage, Basement 1 I like to dig E1 from there first.

#map list {roomarea} {Zephyr Inc}
#map dig e1
e1

I'm going to use the names in this example in place of VNUMs, please use the VNUM instead.

Inside of E1
#map dig b1 Living Quarters & Child Care, Basement 1
#map dig f1 Main Lobby, Floor 1
#map dig f2 Main Lobby, Floor 2
#map dig f3 Main Lobby, Floor 3
#map dig f4 Circular Hallway, Floor 4
#map dig b2 Dormitories
#map dig b3 new
#map info

I hadn't been to b3 yet, new is optional, but clarifies the directions. The NEXT info command shows there isn't an exit back to e1 yet. The dig command will be used to create one (use the VNUM from the info run while above, inside of E1).

b3
#map info
#map dig o VNUM
o
b2
#map dig e1 VNUM
e1
f1
#map dig e1 VNUM
e1
f2
#map dig e1 VNUM
e1
f3
#map dig e1 VNUM
e1
f4
#map dig e1 VNUM

Delete / Remove an Exit (unlink)

The command isn't any of the words you'd probably think to look for; Not Delete (which kills a room, by VNUM or relative exit towards), Nor is it Remove (not a command), it's Unlink.

#map unlink ExitDirection

Global Teleports

A special, in this case for normal players fake, room is needed for holding a list of 'exits' that work globally (anywhere).

In my example beneath, I went to my respawn point and decided to call 'GLOBAL', an exit that doesn't exist, the way to the global room.

Note your current Room vnum: room number to return to, this will be called VNUMBACK later. Then move the context of the map system to the newly created GLOBAL room.

#map info
#map dig GLOBAL
#map goto VNUM(from dig's new room)
#map set {roomarea} {GLOBAL}
#map set {roomname} {GLOBAL}
#map info

You can use #map list {roomarea} {GLOBAL} to find this room's vnum again, these don't all have to be added at the same time; you can return later to add new 'global exists' as you find more one line commands that uniquely take you to an exact room. This is less helpful for, E.G. scripted NPC movements (like going to bed with an NPC in their room, or to another city).

#map list {roomarea} {GLOBAL}
#map goto VNUM
#map dig gohome VNUM
#map dig home VNUM
#map dig respawn VNUM
#map dig upgrade dojo VNUM
#map dig tport ...
...
#map goto VNUMBACK (that VNUM of where you actually are on the MUCK / MUD)

Eureka

Eureka is recent enough to have an OK coverage on the official map. However as you can see from glancing at it, there will need to be a judicious use of void (but not hidden) rooms to make the scaling match.

#map insert (directionback) void

The actual bad part comes with inserting void rooms to join spans.

#map leave
#map return
(Note the room number)
... Go around the gap
#map dig ((direction of gap)) ((actual room number))
#map insert ((direction of gap)) void
(Note the ***void***room number)
((direction of gap; this moves you to the side that's missing a - (has a space instead) ))
#map dig ((Direction towards that new voidroom)) ((voidroom number))

Off the Map

This pair of commands allows you to stop adding to the map automatically, and then toggle it back on when you've returned to the same room you started the command inside of.

#map leave
#map return
OR
#map goto VNUM (useful if you gohome / etc)

As another reminder, you can also search for the room area (between the [ ] brackets) to get a room VNUM to return to if you've gone to a different spot.

#map list {roomarea} {AREA NAME}

Windows 10 WSL

If you'd like to use TinTin++ on Windows, installing a WSL based distribution such as Debian is highly recommended. If you have experience with a different distribution maintaining a server, including getting perl5 dependencies installed based on error messages, then go ahead and go with that. Debian has been selected here for being focused on Freedom, Liberty, and Stability, as well as generally providing clear directions for upgrades.

Note: Windows 10 S tries to be a "safe" OS that restricts user freedom, and thus won't run even WSL. They also offer an upgrade, for a fee, to normal-freedom Windows 10 (no S).

Please follow Debian's official guide for installing on WSL. It will likely have links updated as versions of things change.

History

TinTin++ is the result of almost 30 years of development by a series of programmers. While an absolute dinosaur of a computer from the era of it's origin in the mid 1990s probably couldn't run it today, just about anything from the last 20 years probably can.

Several older versions of this wiki page exist (updated during 2013 and 2015) with the final version of that series updated on 2015-08-25T14:09:09 you might find Anden's examples useful if interested in battle tactics, though they're written assuming python2 (which is no longer supported); while Inutt's code repository uses some perl5 helpers that could be replaced or omitted entirely without breaking the basic multipane functionality. (They help produce the formatted status window and optionally near realtime weather.)

Inutt, one of the coding wizards for FS, has a nice setup for TinTin that's published on their GitLab page. Ketsueki made some small contributions (currently a pull request) to make it more suitable for users with different terminal sizes. Though it does expect a computer screen capable of displaying at least 120 columns of characters across (in that case a split of about 29 for the map and status area, and thus 90 or so for the main text area is recommended). If you're on a 1080p+ monitor, using a normal Graphical User Interface (E.G. Windows, OS X, anything else that runs a web browser) and using a full-screen "terminal" program of any sort (find one you like for your OS) you probably do have that many columns.