Compare commits
22 commits
6e5782f994
...
7e16936658
Author | SHA1 | Date | |
---|---|---|---|
7e16936658 | |||
60128f45cc | |||
3b5ce16ced | |||
c8de3c2070 | |||
pwgen2155 | d6153956bf | ||
pwgen2155 | c6b2b8edba | ||
pwgen2155 | 6b2c711ee6 | ||
Mediaman | 852caf73e5 | ||
730c96191a | |||
f7dd063aab | |||
pwgen2155 | e320d01ca0 | ||
pwgen2155 | f28827d99d | ||
DataHoarder | d4c391bb38 | ||
pwgen2155 | 30bf0c4421 | ||
bc18d5e19d | |||
3ba52fb424 | |||
pwgen2155 | 78aa924279 | ||
Mediaman | 24d8ca6650 | ||
pwgen2155 | 7aa7a5eb5c | ||
pwgen2155 | e7b8f35b02 | ||
d9060c6748 | |||
190c994dca |
|
@ -36,7 +36,7 @@ steps:
|
|||
key:
|
||||
from_secret: ssh_key
|
||||
script:
|
||||
- git pull --ff && sudo docker compose up -d --build
|
||||
- git fetch && git checkout main --force && git pull && sudo docker compose up -d --build
|
||||
depends_on:
|
||||
- test
|
||||
trigger:
|
||||
|
|
|
@ -25,15 +25,6 @@ parameters explained by the comments in the file.
|
|||
If you don't have a clean install, you should instead look at the
|
||||
`install.sh` script and use the pieces that work for your setup.
|
||||
|
||||
## Migrating from IdleRPG
|
||||
|
||||
DawdleRPG is capable of being a drop-in replacement.
|
||||
|
||||
- Run `dawdle.py <path to old irpg.conf>`
|
||||
|
||||
If you have any command line overrides to the configuration, you will
|
||||
need to replace them with the `-o key=value` option.
|
||||
|
||||
## Differences from IdleRPG
|
||||
|
||||
- Names, items, and durations are in different colors.
|
||||
|
|
|
@ -7,28 +7,27 @@ C DIED... and isekai'd
|
|||
|
||||
# Godsends - these subtract from a player's time to level
|
||||
G was sprinkled with fairy dust, or something...
|
||||
G found am unreleased Anime BD
|
||||
G found an unreleased Anime BD
|
||||
G found the glorious wonders of linux
|
||||
G met a friendly AB user, in the toilet
|
||||
G rolled an SSS waifu
|
||||
G rolled a 5* waifu
|
||||
G rewrote this Godsends in Go instead of Perl
|
||||
|
||||
# Quest mode 1 - these are simply timed quests
|
||||
Q1 solve the theft of the AB domain
|
||||
Q1 deliver a priceless scroll to the library of Chinese Anime
|
||||
Q1 free the AB domain from the evil registra. The 4 hereos must venture fourth without delay
|
||||
Q1 deliver a priceless scroll to the library of Chinese Anime, where they will upscale it for you
|
||||
Q1 rescue an imprisoned precure girl from Grefog
|
||||
Q1 find the hidden pics of famous Vtubers
|
||||
Q1 unearth the secret of the Twin Peeks (.)(.)
|
||||
Q1 throwing a party for Animefield
|
||||
Q1 decipher the fabled tablet of AnimeFriends
|
||||
Q1 stay connected longer than the IdleBot (it isn't hard)
|
||||
Q1 walk around aimlessly waiting for new anime
|
||||
|
||||
# Quest mode 2 - these are two-stage navigation quests
|
||||
Q2 464 23 113 187 search for a hostage nymph and heal her broken spring
|
||||
Q2 321 488 137 56 complete the Pisan pilgrimage from the shine of BakaBT to
|
||||
AnimeBytes
|
||||
Q2 304 269 70 417 secretly follow a Heroes part without being invited to join
|
||||
them saving the world
|
||||
Q2 447 432 359 318 travel to a tiny country village in Hokaido to start a
|
||||
rebellion of Anime
|
||||
Q2 326 31 246 133 must spring trapped girls from the dungeons of Altis and
|
||||
return them to their rightful homes
|
||||
Q2 321 488 137 56 complete the Pisan pilgrimage from the shine of BakaBT to AnimeBytes
|
||||
Q2 304 269 70 417 secretly follow a Heroes party. Without being seen, and help them save the world
|
||||
Q2 447 432 359 318 travel to a tiny country village in Hokkaido to start a rebellion of Anime
|
||||
Q2 326 31 246 133 must spring trapped girls from the dungeons of Altis and return them to their rightful homes
|
||||
Q2 1 1 345 345 search for Ikargua's hammer and return it to him in one piece, or multiple pieces, or to just give him the shattered remains of a hammer, either is good.
|
||||
|
|
|
@ -136,13 +136,7 @@ def start_bot() -> None:
|
|||
log.info("Bot %s starting.", bot.VERSION)
|
||||
|
||||
store: bot.GameStorage
|
||||
if conf.get("store_format") == "idlerpg":
|
||||
store = bot.IdleRPGGameStorage(bot.datapath(conf.get("dbfile")))
|
||||
elif conf.get("store_format") == "sqlite3":
|
||||
store = bot.Sqlite3GameStorage(bot.datapath(conf.get("dbfile")))
|
||||
else:
|
||||
sys.stderr.write(f"Invalid configuration store_format={conf.get('store_format')}. Configuration must be idlerpg or sqlite3.")
|
||||
sys.exit(2)
|
||||
store = bot.Sqlite3GameStorage(bot.datapath(conf.get("dbfile")))
|
||||
|
||||
if conf.get("setup"):
|
||||
if store.exists():
|
||||
|
|
284
dawdle/bot.py
284
dawdle/bot.py
|
@ -390,250 +390,6 @@ class GameStorage(object):
|
|||
raise NotImplementedError
|
||||
|
||||
|
||||
class IdleRPGGameStorage(GameStorage):
|
||||
"""Implements a GameStorage compatible with the IdleRPG db.
|
||||
|
||||
Since the IdleRPG db is a tsv file, we can't reasonably update it
|
||||
piecemeal, so the playerbase is cached during reads, and written
|
||||
in its entirety on writes. The player objects are shared between
|
||||
the cache and the GameDB.
|
||||
|
||||
"""
|
||||
|
||||
IRPG_FIELDS = ["username", "pass", "is admin", "level", "class", "next ttl", "nick", "userhost", "online", "idled", "x pos", "y pos", "pen_mesg", "pen_nick", "pen_part", "pen_kick", "pen_quit", "pen_quest", "pen_logout", "created", "last login", "amulet", "charm", "helm", "boots", "gloves", "ring", "leggings", "shield", "tunic", "weapon", "alignment"]
|
||||
IRPG_FIELD_COUNT = len(IRPG_FIELDS)
|
||||
|
||||
# Instead of names, idlerpg decided to tack on codes to the number.
|
||||
ITEMCODES = {
|
||||
"Mattt's Omniscience Grand Crown":"a",
|
||||
"Juliet's Glorious Ring of Sparkliness":"h",
|
||||
"Res0's Protectorate Plate Mail":"b",
|
||||
"Dwyn's Storm Magic Amulet":"c",
|
||||
"Jotun's Fury Colossal Sword":"d",
|
||||
"Drdink's Cane of Blind Rage":"e",
|
||||
"Mrquick's Magical Boots of Swiftness":"f",
|
||||
"Jeff's Cluehammer of Doom":"g"
|
||||
}
|
||||
|
||||
_dbpath: str
|
||||
_cache: List[Player]
|
||||
|
||||
def _code_to_item(self, s: str) -> Tuple[int, str]:
|
||||
"""Converts an IdleRPG item code to a tuple of (level, name)."""
|
||||
match = re.match(r"(\d+)(.?)", s)
|
||||
if not match:
|
||||
log.error(f"invalid item code: {s}")
|
||||
return (int(s), "")
|
||||
lvl = int(match[1])
|
||||
if match[2]:
|
||||
return (lvl, [k for k,v in IdleRPGGameStorage.ITEMCODES.items() if v == match[2]][0])
|
||||
return (lvl, "")
|
||||
|
||||
|
||||
def _item_to_code(self, level: int, name: str) -> str:
|
||||
"""Converts an item level and name to an IdleRPG item code."""
|
||||
return f"{level}{IdleRPGGameStorage.ITEMCODES.get(name, '')}"
|
||||
|
||||
|
||||
def __init__(self, dbpath: str):
|
||||
self._dbpath = dbpath
|
||||
self._cache = []
|
||||
|
||||
|
||||
def create(self) -> None:
|
||||
"""Creates a new IdleRPG db."""
|
||||
self.write([])
|
||||
|
||||
|
||||
def exists(self) -> bool:
|
||||
"""Returns true if the db file exists."""
|
||||
return os.path.exists(self._dbpath)
|
||||
|
||||
|
||||
def close(self) -> None:
|
||||
"""Does nothing - no need to close the idlerpg file."""
|
||||
pass
|
||||
|
||||
|
||||
def backup(self) -> None:
|
||||
"""Backs up database to a directory."""
|
||||
os.makedirs(datapath(conf.get("backupdir")), exist_ok=True)
|
||||
backup_path = os.path.join(datapath(conf.get("backupdir")),
|
||||
f"{time.strftime('%Y-%m-%dT%H:%M:%S')}-{os.path.basename(conf.get('dbfile'))}")
|
||||
shutil.copyfile(self._dbpath, backup_path)
|
||||
|
||||
|
||||
def _player_to_record(self, p: Player) -> str:
|
||||
"""Converts the player to an IdleRPG db record."""
|
||||
return "\t".join([
|
||||
p.name,
|
||||
p.pw,
|
||||
"1" if p.isadmin else "0",
|
||||
str(p.level),
|
||||
p.cclass,
|
||||
str(p.nextlvl),
|
||||
p.nick,
|
||||
p.userhost,
|
||||
"1" if p.online else "0",
|
||||
str(p.idled),
|
||||
str(p.posx),
|
||||
str(p.posy),
|
||||
str(p.penmessage),
|
||||
str(p.pennick),
|
||||
str(p.penpart),
|
||||
str(p.penkick),
|
||||
str(p.penquit + p.pendropped),
|
||||
str(p.penquest),
|
||||
str(p.penlogout),
|
||||
str(int(p.created.timestamp())),
|
||||
str(int(p.lastlogin.timestamp())),
|
||||
self._item_to_code(p.item_level("amulet"), p.item_name("amulet")),
|
||||
self._item_to_code(p.item_level("charm"), p.item_name("charm")),
|
||||
self._item_to_code(p.item_level("helm"), p.item_name("helm")),
|
||||
self._item_to_code(p.item_level("boots"), p.item_name("boots")),
|
||||
self._item_to_code(p.item_level("gloves"), p.item_name("gloves")),
|
||||
self._item_to_code(p.item_level("ring"), p.item_name("ring")),
|
||||
self._item_to_code(p.item_level("leggings"), p.item_name("leggings")),
|
||||
self._item_to_code(p.item_level("shield"), p.item_name("shield")),
|
||||
self._item_to_code(p.item_level("tunic"), p.item_name("tunic")),
|
||||
self._item_to_code(p.item_level("weapon"), p.item_name("weapon")),
|
||||
str(p.alignment)
|
||||
]) + "\n"
|
||||
|
||||
|
||||
def readall(self) -> Iterable[Player]:
|
||||
"""Reads all the players into a dict."""
|
||||
self._cache = []
|
||||
with open(self._dbpath) as inf:
|
||||
for line in inf.readlines():
|
||||
if re.match(r'\s*(?:#|$)', line):
|
||||
continue
|
||||
parts = line.rstrip().split("\t")
|
||||
if len(parts) != IdleRPGGameStorage.IRPG_FIELD_COUNT:
|
||||
log.critical("line corrupt in player db - %d fields: %s", len(parts), repr(line))
|
||||
sys.exit(-1)
|
||||
|
||||
# This makes a mapping from irpg field to player field.
|
||||
d = dict(zip(["name", "pw", "isadmin", "level", "cclass", "nextlvl", "nick", "userhost", "online", "idled", "posx", "posy", "penmessage", "pennick", "penpart", "penkick", "penquit", "penquest", "penlogout", "created", "lastlogin", "amulet", "charm", "helm", "boots", "gloves", "ring", "leggings", "shield", "tunic", "weapon", "alignment"], parts))
|
||||
# convert items
|
||||
items = dict()
|
||||
for i in Item.SLOTS:
|
||||
level, name = self._code_to_item(d[i])
|
||||
if level > 0:
|
||||
items[i] = Item(level, name)
|
||||
del d[i]
|
||||
|
||||
# convert int fields
|
||||
for f in ["level", "nextlvl", "idled", "posx", "posy", "penmessage", "pennick", "penpart", "penkick", "penquit", "penquest", "penlogout", "created", "lastlogin"]:
|
||||
d[f] = round(float(d[f])) # type:ignore
|
||||
# convert boolean fields
|
||||
for f in ["isadmin", "online"]:
|
||||
d[f] = (d[f] == '1') # type:ignore
|
||||
|
||||
d['email'] = "" # needed to pass testing
|
||||
|
||||
d['pendropped'] = 0 # type: ignore
|
||||
|
||||
d['created'] = datetime.datetime.fromtimestamp(int(d['created'])) # type:ignore
|
||||
d['lastlogin'] = datetime.datetime.fromtimestamp(int(d['lastlogin'])) # type:ignore
|
||||
|
||||
p = Player.from_dict(d)
|
||||
p.items = items
|
||||
self._cache.append(p)
|
||||
return self._cache
|
||||
|
||||
|
||||
def write(self, players: Iterable[Player]) -> None:
|
||||
"""Writes players to an IdleRPG db."""
|
||||
with open(self._dbpath, "w") as ouf:
|
||||
ouf.write("# " + "\t".join(IdleRPGGameStorage.IRPG_FIELDS) + "\n")
|
||||
for p in self._cache:
|
||||
ouf.write(self._player_to_record(p))
|
||||
|
||||
|
||||
def new(self, p: Player) -> None:
|
||||
"""Creates a new player in the db."""
|
||||
self._cache.append(p)
|
||||
with open(self._dbpath, "a") as ouf:
|
||||
ouf.write(self._player_to_record(p))
|
||||
|
||||
|
||||
def rename_player(self, old_name: str, new_name: str) -> None:
|
||||
"""Renames a player in the db."""
|
||||
# Presumably the player in the cache has changed its name, so
|
||||
# just write out the file.
|
||||
self.write([])
|
||||
|
||||
|
||||
def delete_player(self, pname:str) -> None:
|
||||
"""Removes a player from the db."""
|
||||
self._cache = [p for p in self._cache if p.name != pname]
|
||||
self.write([])
|
||||
|
||||
|
||||
def add_history(self, pnames: List[str], text: str) -> None:
|
||||
"""Adds history text for the player.
|
||||
|
||||
IdleRPG dumps all history into a single modsfile, which is
|
||||
then grepped for by the website.
|
||||
|
||||
"""
|
||||
with open(datapath(conf.get("modsfile")), "a") as ouf:
|
||||
ouf.write(f"[{time.strftime('%m/%d/%y %H:%M:%S')}] {text}\n")
|
||||
|
||||
|
||||
def read_quest(self) -> Optional[Quest]:
|
||||
"""Returns stored Quest object or None."""
|
||||
if not conf.get("writequestfile"):
|
||||
return None
|
||||
if os.stat(datapath(conf.get("questfilename"))).st_size == 0:
|
||||
return None
|
||||
|
||||
with open(datapath(conf.get("questfilename"))) as inf:
|
||||
q = Quest()
|
||||
for line in inf.readlines():
|
||||
key, val = line.split(' ', 2)
|
||||
if key == "T":
|
||||
q.text = val
|
||||
elif key == "Y":
|
||||
q.mode = int(val)
|
||||
elif key == "S":
|
||||
if q.mode == 1:
|
||||
q.qtime = int(val)
|
||||
else:
|
||||
q.stage = int(val)
|
||||
elif key == "P":
|
||||
for pair in chunk.chunk(val.split(' '), 2):
|
||||
q.dests.append((int(pair[0]), int(pair[1])))
|
||||
elif re.match("P\d", key):
|
||||
q.questor_names.append(val)
|
||||
return q
|
||||
|
||||
|
||||
def update_quest(self, quest: Optional[Quest]) -> None:
|
||||
"""Updates quest information in store."""
|
||||
if not conf.get("writequestfile"):
|
||||
return
|
||||
with open(datapath(conf.get("questfilename")), 'w') as ouf:
|
||||
if not quest:
|
||||
# leave behind an empty quest file
|
||||
return
|
||||
|
||||
ouf.write(f"T {quest.text}\n"
|
||||
f"Y {quest.mode}\n")
|
||||
|
||||
if quest.mode == 1:
|
||||
ouf.write(f"S {quest.qtime}\n")
|
||||
elif quest.mode == 2:
|
||||
ouf.write(f"S {quest.stage:d}\n"
|
||||
f"P {' '.join([' '.join([str(c) for c in p]) for p in quest.dests])}\n")
|
||||
|
||||
ouf.write(f"P1 {quest.questors[0].name}\n"
|
||||
f"P2 {quest.questors[1].name}\n"
|
||||
f"P3 {quest.questors[2].name}\n"
|
||||
f"P4 {quest.questors[3].name}\n")
|
||||
|
||||
|
||||
class Sqlite3GameStorage(GameStorage):
|
||||
"""Player store using sqlite3."""
|
||||
|
||||
|
@ -1181,10 +937,10 @@ class DawdleBot(abstract.AbstractBot):
|
|||
|
||||
|
||||
def channel_message(self, user: abstract.AbstractClient.User, text: str) -> None:
|
||||
"""Called when channel message received."""
|
||||
"""Treat channel messages like private messages."""
|
||||
player = self._db.from_user(user)
|
||||
if player:
|
||||
self.penalize(player, "message", text)
|
||||
if text[0] is '!' and player:
|
||||
self.private_message(player, text[1:])
|
||||
|
||||
|
||||
def channel_notice(self, user: abstract.AbstractClient.User, text: str) -> None:
|
||||
|
@ -2121,7 +1877,7 @@ class DawdleBot(abstract.AbstractBot):
|
|||
special_items = [SpecialItem(25, 50, 25, 'helm', "Stone Mask from JoJo",
|
||||
"Your enemies quiver before your sacrificial device."),
|
||||
SpecialItem(25, 50, 25, 'ring', "Howl's Ring of Love",
|
||||
"Your lifeforce is are binded to a fair maiden "
|
||||
"Your lifeforce is bounded to a fair maiden "
|
||||
"What more could you ask for?"),
|
||||
SpecialItem(30, 75, 25, 'tunic', "Mahou Shoujo Pantsu",
|
||||
"Your enemies cower in fear as their attacks have no effect on you "
|
||||
|
@ -2138,9 +1894,10 @@ class DawdleBot(abstract.AbstractBot):
|
|||
SpecialItem(48, 250, 51, 'boots', "The Thigh Highs of Thighness",
|
||||
"Your enemies are left choking on your dust as you run from them "
|
||||
"very, very quickly."),
|
||||
SpecialItem(25, 300, 51, 'weapon', "Jeff's Cluehammer of Doom",
|
||||
"It has bought doom to your enemies, your family, and your "
|
||||
"possible friends. Gods error, in your favour")]
|
||||
SpecialItem(25, 300, 51, 'weapon', "Jeff's Cluelesshammer of Doom",
|
||||
"It has brought doom to your enemies, your family, and your "
|
||||
"possible friends. God made a mistake, and you paid the price for it"
|
||||
"In the end, it is just a hammer.")]
|
||||
|
||||
for si in special_items:
|
||||
if player.level >= si.minlvl and rand.randomly('specitem_find', 40):
|
||||
|
@ -2156,6 +1913,7 @@ class DawdleBot(abstract.AbstractBot):
|
|||
self.notice(player.nick,
|
||||
f"The light of the gods shines down upon you! You have "
|
||||
f"found the S Tier {C('item')}level {ilvl} {si.name}{C()}! {si.flavor}")
|
||||
self.chanmsg(f"{C('name', player.name)} - S Tier {C('item')}Level {ilvl}{C()} Item Acquired"),
|
||||
player.acquire_item(si.kind, ilvl, si.name)
|
||||
self._db.write_players([player])
|
||||
return
|
||||
|
@ -2208,7 +1966,7 @@ class DawdleBot(abstract.AbstractBot):
|
|||
f"{duration(penalty)} is added to {C('name', opp.name)}'s clock.")
|
||||
opp.nextlvl += penalty
|
||||
self.chanmsg(f"{C('name', opp.name)} reaches next level in {duration(opp.nextlvl)}.")
|
||||
if rand.randomly('insult', 0, 2):
|
||||
if rand.randomly('insult', 2):
|
||||
self.chanmsg(f"the baka")
|
||||
elif player.level > 19 and rand.randomly('pvp_swap_item', 25):
|
||||
slot = rand.choice('pvp_swap_itemtype', Item.SLOTS)
|
||||
|
@ -2234,7 +1992,7 @@ class DawdleBot(abstract.AbstractBot):
|
|||
self.logchanmsg([player], "While recovering from a strenuous battle of thumb wrestling "
|
||||
f"{C('name', player.name)} notices something "
|
||||
f"in the mud. Upon investigation, they find an old lost item!")
|
||||
if rand.randomly('insult', 0, 2):
|
||||
if rand.randomly('insult', 2):
|
||||
self.chanmsg(f"the lucky baka")
|
||||
self.find_item(player)
|
||||
|
||||
|
@ -2301,7 +2059,7 @@ class DawdleBot(abstract.AbstractBot):
|
|||
elif slot == "boots":
|
||||
msg = f"{C('name', player.name)} stepped in some hot lava!"
|
||||
self.logchanmsg([player], msg + f" {C('name')}{player.name}'s{C()} {C('item', Item.DESC[slot])} loses 10% of its effectiveness.")
|
||||
if rand.randomly('insult', 0, 2):
|
||||
if rand.randomly('insult', 2):
|
||||
self.chanmsg(f"baka")
|
||||
player.items[slot].level = int(player.items[slot].level * 0.9)
|
||||
return
|
||||
|
@ -2347,7 +2105,7 @@ class DawdleBot(abstract.AbstractBot):
|
|||
msg = f"A sorceror enchanted {C('name')}{player.name}'s{C()} {C('item', 'boots')} with Swiftness!"
|
||||
|
||||
self.logchanmsg([player], msg + f" {C('name')}{player.name}'s{C()} {C('item', Item.DESC[slot])} gains 10% effectiveness.")
|
||||
if rand.randomly('insult', 0, 2):
|
||||
if rand.randomly('insult', 2):
|
||||
self.chanmsg(f"WTFBBQ")
|
||||
player.items[slot].level = int(player.items[slot].level * 1.1)
|
||||
return
|
||||
|
@ -2385,14 +2143,14 @@ class DawdleBot(abstract.AbstractBot):
|
|||
f"You made to steal {C('name', target.name)}'s {C('item', Item.DESC[slot])}, "
|
||||
f"but realized it was lower level than your own. You creep "
|
||||
f"back into the shadows.")
|
||||
if rand.randomly('insult', 0, 2):
|
||||
if rand.randomly('insult', 2):
|
||||
self.notice(player.nick, (f"you creep."))
|
||||
else:
|
||||
amount = int(player.nextlvl * rand.randint('evilness_penalty_pct', 1,6) / 100)
|
||||
player.nextlvl += amount
|
||||
self.logchanmsg([player], f"{C('name', player.name)} is forsaken by their evil god. {duration(amount)} is "
|
||||
f"added to their clock.")
|
||||
if rand.randomly('insult', 0, 2):
|
||||
if rand.randomly('insult', 2):
|
||||
self.chanmsg(f"YOUR INSOLENCE WILL NOT BE FORGOTTEN {C('name', player.name)}")
|
||||
if player.nextlvl > 0:
|
||||
self.chanmsg(f"{C('name', player.name)} reaches next level in {duration(player.nextlvl)}.")
|
||||
|
@ -2431,8 +2189,8 @@ class DawdleBot(abstract.AbstractBot):
|
|||
destx = self._db._quest.dests[self._db._quest.stage-1][0]
|
||||
desty = self._db._quest.dests[self._db._quest.stage-1][1]
|
||||
for p in self._db._quest.questors:
|
||||
if not rand.randomly("quest_movement", 10):
|
||||
# Move at 10% speed when questing.
|
||||
if not rand.randomly("quest_movement", 20):
|
||||
# Move at 5% speed when questing.
|
||||
op = [x for x in op if x != p]
|
||||
continue
|
||||
# mode 2 questors always move towards the next goal
|
||||
|
@ -2464,9 +2222,9 @@ class DawdleBot(abstract.AbstractBot):
|
|||
combatant = combatants[(p.posx, p.posy)]
|
||||
if combatant.isadmin and rand.randomly('move_player_bow', 100):
|
||||
self.chanmsg(f"{C('name', p.name)} encounters {C('name', combatant.name)} and bows humbly, knowing the end is neigh")
|
||||
if rand.randomly('insult', 0, 2):
|
||||
if rand.randomly('insult', 2):
|
||||
self.chanmsg(f"Their heads hit as if a comedy routine is playing out before our eyes"
|
||||
"Hot and flusted their cheeks turn red from embarressment")
|
||||
"Hot and flustered their cheeks turn red from embarrassment")
|
||||
elif rand.randomly('move_player_combat', len(op)):
|
||||
self.pvp_battle(p, combatant,
|
||||
"come upon",
|
||||
|
@ -2499,8 +2257,8 @@ class DawdleBot(abstract.AbstractBot):
|
|||
self.chanmsg(f"{C('name', qp[0].name)}, {C('name', qp[1].name)}, {C('name', qp[2].name)}, and {C('name', qp[3].name)} have "
|
||||
f"been chosen by the gods to {new_quest.text}. Quest to end in "
|
||||
f"{duration(quest_time)}.")
|
||||
if rand.randomly('insult', 0, 2):
|
||||
self.chanmsg(f"or, like, whenever... what's even the point? They only walk from here to there, anyone could do that"
|
||||
if rand.randomly('insult', 2):
|
||||
self.chanmsg(f"or, like, whenever... what's even the point? They only walk from here to there, anyone could do that "
|
||||
"I'm not like... going to join them or anything... not even if you asked")
|
||||
log.info("Starting mode 1 quest with duration %s", duration(quest_time))
|
||||
elif match[1] == '2':
|
||||
|
|
|
@ -49,7 +49,6 @@ def read_config(path: str) -> Dict[str, Any]:
|
|||
"allylvlstep": 1.16,
|
||||
"allymaxexplvl": 60,
|
||||
"backupdir": ".dbbackup",
|
||||
"store_format": "idlerpg",
|
||||
"daemonize": True,
|
||||
"loglevel": "DEBUG",
|
||||
"throttle": True,
|
||||
|
|
|
@ -11,27 +11,58 @@ def randomly(key: str, odds: int) -> bool:
|
|||
"""Overrideable random func which returns true at 1:ODDS odds."""
|
||||
if key in overrides:
|
||||
return cast(bool, overrides[key])
|
||||
return random.randint(0, odds-1) < 1
|
||||
ans = random.randint(0, odds-1) < 1
|
||||
try:
|
||||
log.debug(f"Rand: {key}: {odds} A: {ans}")
|
||||
except Exception:
|
||||
pass
|
||||
return ans
|
||||
|
||||
|
||||
def randint(key: str, bottom: int, top: int) -> int:
|
||||
"""Overrideable random func which returns an integer bottom <= i <= top."""
|
||||
if key in overrides:
|
||||
return cast(int, overrides[key])
|
||||
return random.randint(int(bottom), int(top))
|
||||
ans = random.randint(int(bottom), int(top))
|
||||
try:
|
||||
# Very Very Verbose...
|
||||
if key not in [
|
||||
'move_player_x',
|
||||
'move_player_y',
|
||||
'hog_trigger',
|
||||
'team_battle_trigger',
|
||||
'calamity_trigger',
|
||||
'godsend_trigger',
|
||||
'evilness_trigger',
|
||||
'goodness_trigger'
|
||||
]:
|
||||
log.debug(f"Rand: {key}: {int(bottom)} to {int(top)} A: {ans}")
|
||||
except Exception:
|
||||
pass
|
||||
return ans
|
||||
|
||||
|
||||
def gauss(key: str, mu: float, sigma: float) -> int:
|
||||
"""Overrideable func which returns an random int with gaussian distribution."""
|
||||
if key in overrides:
|
||||
return cast(int, overrides[key])
|
||||
return int(random.gauss(mu, sigma))
|
||||
ans = int(random.gauss(mu, sigma))
|
||||
try:
|
||||
log.debug(f"Rand: {key}: A: {ans}")
|
||||
except Exception:
|
||||
pass
|
||||
return ans
|
||||
|
||||
def sample(key: str, seq: Sequence[T], count: int) -> List[T]:
|
||||
"""Overrideable random func which returns random COUNT elements of SEQ."""
|
||||
if key in overrides:
|
||||
return cast(List[T], overrides[key])
|
||||
return random.sample(seq, count)
|
||||
ans = random.sample(seq, count)
|
||||
try:
|
||||
log.debug(f"Rand: {key}: A:{ans}")
|
||||
except Exception:
|
||||
pass
|
||||
return ans
|
||||
|
||||
|
||||
def choice(key: str, seq: Sequence[T]) -> T:
|
||||
|
@ -40,7 +71,12 @@ def choice(key: str, seq: Sequence[T]) -> T:
|
|||
return cast(T, overrides[key])
|
||||
# Don't use random.choice here - it uses random access, which
|
||||
# is unsupported by the dict_keys view.
|
||||
return random.sample(seq, 1)[0]
|
||||
ans = random.sample(seq, 1)[0]
|
||||
try:
|
||||
log.debug(f"Rand: {key}: {p.nick for p in seq} A: {ans.nick}")
|
||||
except Exception:
|
||||
pass
|
||||
return ans
|
||||
|
||||
|
||||
def shuffle(key: str, seq: MutableSequence[Any]) -> None:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
rm /opt/dawdlerpg/site/static/map.mp4;
|
||||
|
||||
ffmpeg -framerate 3 -pattern_type glob -i '/opt/dawdlerpg/map/*.png' \
|
||||
ffmpeg -framerate 30 -pattern_type glob -i '/opt/dawdlerpg/map/*.png' \
|
||||
-c:v libx264 -pix_fmt yuv420p \
|
||||
/opt/dawdlerpg/site/static/map.mp4
|
||||
|
||||
|
|
|
@ -185,20 +185,10 @@ itemcolor yellow
|
|||
# file's directory by default. Uncomment if you need to specify it.
|
||||
# datadir <data directory>
|
||||
|
||||
# Filename for player database. This should be set to the
|
||||
# game.sqlite3 in the site folder if using the django website.
|
||||
dbfile dawdle.db
|
||||
# To integrate with website, use this. Create this file with:
|
||||
# manager.py migrate --database=game
|
||||
# before running with --setup
|
||||
# dbfile ../site/game.sqlite3
|
||||
|
||||
# Game store format - "idlerpg" is fully compatible with the original
|
||||
# perl idlerpg bot and website so dawdlerpg can be used as a drop-in
|
||||
# replacement. "sqlite3" allows some of the newer features and works
|
||||
# with the django website.
|
||||
# store_format idlerpg
|
||||
store_format sqlite3
|
||||
dbfile ../site/game.sqlite3
|
||||
|
||||
# Filename for events file.
|
||||
eventsfile events.txt
|
||||
|
|
BIN
setup/dawdle.db
BIN
setup/dawdle.db
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
1
|
|
@ -6,10 +6,13 @@
|
|||
<h1>Timelapse of movements</h1>
|
||||
<p>A Timelapse of everyone since:
|
||||
Mon, Feb 26 00:05:24 UTC 2024 </p>
|
||||
{% with timelapse='Wed Feb 28 04:01:29 UTC 2024' %}
|
||||
{% with timelapse='DEFAULT' %}
|
||||
<p> Last Generated: {{ timelapse }} </p>
|
||||
<p> Video @ 30 FPS at 1 minute per frame. (1 second = 30 minutes of gameplay)</p>
|
||||
{% endwith %}
|
||||
<video controls width="500"> <source src="/static/map.mp4" type="video/mp4" />
|
||||
<video controls width="500"> <source src="/static/map.mp4" type="video/mp4" /></video>
|
||||
<p> Original Week 1: (1 frame is 15 minutes of gameplay) <p>
|
||||
<video controls width="500"> <source src="/static/map-week-1.mp4" type="video/mp4" /></video>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Reference in a new issue