Compare commits

..

22 commits

Author SHA1 Message Date
pwgen2155 7e16936658
WIP: penalties
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-04 22:38:28 +00:00
pwgen2155 60128f45cc fix: spelling of quests is hard
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-04 22:10:55 +11:00
pwgen2155 3b5ce16ced feat: hotfix channel messages & random
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-04 21:31:40 +11:00
pwgen2155 c8de3c2070 fix: only look if it has !
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-04 21:24:45 +11:00
pwgen2155 d6153956bf
feat: new quests, treat public channel as private, prove randomness (#8)
All checks were successful
continuous-integration/drone/push Build is passing
Co-authored-by: pwgen2155 <git@example.com>
Reviewed-on: #8
2024-03-04 10:20:24 +00:00
pwgen2155 c6b2b8edba
revert 6b2c711ee6
All checks were successful
continuous-integration/drone/push Build is passing
revert feat: display all randomness (#7)

Co-authored-by: pwgen2155 <git@example.com>
Reviewed-on: #7
2024-03-04 09:29:22 +00:00
pwgen2155 6b2c711ee6
feat: display all randomness (#7)
All checks were successful
continuous-integration/drone/push Build is passing
Co-authored-by: pwgen2155 <git@example.com>
Reviewed-on: #7
2024-03-04 09:06:51 +00:00
Mediaman 852caf73e5
feat: Changes for the removal of IdleRPG db support (#6)
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #6
Reviewed-by: pwgen2155 <pwgen2155@noreply.gammaspectra.live>
Co-authored-by: Mediaman <mediaman@riseup.net>
Co-committed-by: Mediaman <mediaman@riseup.net>
2024-03-04 08:57:25 +00:00
pwgen2155 730c96191a feat: timelapse rework
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-04 15:37:31 +11:00
pwgen2155 f7dd063aab fix: not enough frames
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-04 10:31:41 +11:00
pwgen2155 e320d01ca0
fix: why is pid here
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-03 22:06:05 +00:00
pwgen2155 f28827d99d
fix: rude
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-03 22:05:21 +00:00
DataHoarder d4c391bb38
Include S tier level on channel notice (#5)
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #5
Co-authored-by: DataHoarder <weebdatahoarder@noreply.gammaspectra.live>
Co-committed-by: DataHoarder <weebdatahoarder@noreply.gammaspectra.live>
2024-03-02 20:59:47 +00:00
pwgen2155 30bf0c4421
feat: new quest
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-01 12:17:35 +00:00
pwgen2155 bc18d5e19d fix: always checkout and pull
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-01 19:49:04 +11:00
pwgen2155 3ba52fb424 fix: 5% movements and timelapse support week-1
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-01 17:16:35 +11:00
pwgen2155 78aa924279
fix: events spelling
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-01 05:20:23 +00:00
Mediaman 24d8ca6650
fix: special item typos (#3)
All checks were successful
continuous-integration/drone/push Build is passing
Hammer had a typo in the description

Reviewed-on: #3
Co-authored-by: Mediaman <mediaman@riseup.net>
Co-committed-by: Mediaman <mediaman@riseup.net>
2024-02-29 20:38:16 +00:00
pwgen2155 7aa7a5eb5c
fix: 5* waifu
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-29 12:21:49 +00:00
pwgen2155 e7b8f35b02
Update dawdle/bot.py
All checks were successful
continuous-integration/drone/push Build is passing
25
2024-02-29 12:16:45 +00:00
pwgen2155 d9060c6748 fix: functions are hard
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-29 22:01:02 +11:00
pwgen2155 190c994dca fix: spelling & events
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-29 21:56:49 +11:00
12 changed files with 82 additions and 313 deletions

View file

@ -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:

View file

@ -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.

View file

@ -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.

View file

@ -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():

View file

@ -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':

View file

@ -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,

View file

@ -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:

View file

@ -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

View file

@ -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

Binary file not shown.

View file

@ -1 +0,0 @@
1

View file

@ -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>