2023-02-27 17:12:27 -08:00
const ver _numeric = 0 ;
const ver _string = "pre alpha" ;
2023-02-01 14:52:56 -08:00
const sleep = ms => new Promise ( r => setTimeout ( r , ms ) ) ; // sleep(int ms)
2023-01-28 21:08:47 -08:00
var page _elements = { } ;
2023-02-01 14:52:56 -08:00
var gamestate = { } ;
2023-02-24 12:39:18 -08:00
var ticks _since _last _save = 0 ;
2023-02-01 14:52:56 -08:00
const gamestate _default = {
2023-02-24 12:39:18 -08:00
"statever" : "1" ,
2023-02-01 14:52:56 -08:00
"tick" : 1 ,
2023-02-24 12:39:18 -08:00
"name" : "Nameless" ,
2025-07-31 11:28:09 -07:00
"class" : "Seaglet" ,
2023-02-24 12:39:18 -08:00
"level" : 1 ,
2023-02-27 20:25:39 -08:00
"shinies" : 0 ,
2025-07-29 12:50:35 -07:00
"colony" : 1 ,
"food" : 0 ,
"autosave" : 35 ,
"story_beat" : 0 ,
"xp" : 0 ,
2025-07-31 11:28:09 -07:00
"xp_next" : 50 ,
"enc_human" : "pause" ,
2025-08-22 13:01:58 -07:00
"enc_seagull" : "pause" ,
"agility" : 0 ,
2025-09-02 16:44:28 -07:00
"instinct" : 0 ,
"leadership" : 0
2023-02-01 14:52:56 -08:00
} ;
2023-02-27 20:22:36 -08:00
var bool _log _alt = false
2023-01-28 21:08:47 -08:00
function record _log ( text ) {
const div _logrow = document . createElement ( "div" ) ;
2023-02-27 20:22:36 -08:00
if ( bool _log _alt ) { div _logrow . className = "log-line" ; }
else { div _logrow . className = "log-line-alt" ; }
bool _log _alt = ! bool _log _alt ;
2023-01-28 21:08:47 -08:00
const div _logtick = document . createElement ( "div" ) ;
div _logtick . className = "log-tick"
2023-02-01 14:52:56 -08:00
div _logtick . innerHTML = "Day " + gamestate [ "tick" ] ;
2023-01-28 21:08:47 -08:00
div _logrow . append ( div _logtick ) ;
const div _logmsg = document . createElement ( "div" ) ;
div _logmsg . innerHTML = text ;
div _logmsg . className = "log-msg" ;
div _logrow . append ( div _logmsg ) ;
page _elements [ "div_log" ] . append ( div _logrow ) ;
}
2025-08-05 17:57:43 -07:00
function record _log _with _choices ( ) {
const div _logrow = document . createElement ( "div" ) ;
if ( bool _log _alt ) { div _logrow . className = "log-line" ; }
else { div _logrow . className = "log-line-alt" ; }
bool _log _alt = ! bool _log _alt ;
const div _logtick = document . createElement ( "div" ) ;
div _logtick . className = "log-tick"
div _logtick . innerHTML = "Day " + gamestate [ "tick" ] ;
div _logrow . append ( div _logtick ) ;
const div _logdata = document . createElement ( "div" ) ;
const div _logmsg = document . createElement ( "div" ) ;
div _logmsg . innerHTML = arguments [ 0 ] ;
div _logmsg . className = "log-msg" ;
div _logdata . append ( div _logmsg ) ;
const div _logactions = document . createElement ( "div" ) ;
div _logactions . className = "log-button-row" ;
for ( var i = 1 ; i < arguments . length ; i += 2 ) {
console . log ( i )
var label = arguments [ i ] ;
var callback = arguments [ i + 1 ] ;
var btn _action = document . createElement ( "button" ) ;
btn _action . innerHTML = label ;
2025-08-05 18:09:48 -07:00
btn _action . className = "log-action-button" ;
2025-08-05 17:57:43 -07:00
btn _action . setAttribute ( "onclick" , callback + "; tick_meter_running = true;" ) ;
div _logactions . append ( btn _action ) ;
}
div _logdata . append ( div _logactions ) ;
div _logrow . append ( div _logdata ) ;
page _elements [ "div_log" ] . append ( div _logrow ) ;
tick _meter _running = false ;
}
2025-08-22 13:01:58 -07:00
var modal _dialog _open = false ;
var dialog _queue = [ ] ;
function modal _no _prop ( event ) { event . stopPropagation ( ) ; }
async function open _modal _dialog ( dialog ) {
var modal _background = document . getElementById ( "modal-background" ) ;
if ( ! modal _background ) {
modal _background = document . createElement ( "div" ) ;
modal _background . setAttribute ( "id" , "modal-background" ) ;
}
var modal _root = document . getElementById ( "modal" ) ;
if ( ! modal _root ) {
modal _root = document . createElement ( "div" ) ;
modal _root . setAttribute ( "id" , "modal" ) ;
modal _root . onclick = modal _no _prop ;
modal _background . appendChild ( modal _root ) ;
}
if ( ! modal _dialog _open ) {
tick _meter _running = false ;
modal _dialog _open = true ;
2025-09-02 16:44:28 -07:00
modal _background . style . zIndex = "10 !important" ;
modal _background . style . visibility = "visible !important" ;
2025-08-22 13:01:58 -07:00
}
dialog _data = await fetch ( ` /dialog/ ${ dialog } ` )
. then ( res => { return res . text ( ) ; } ) ;
modal _root . innerHTML = dialog _data ;
}
2025-07-29 12:50:35 -07:00
function update _ui ( ) {
page _elements [ "lbl_name" ] . innerHTML = gamestate [ "name" ] ;
page _elements [ "lbl_tick" ] . innerHTML = gamestate [ "tick" ] ;
page _elements [ "lbl_colony" ] . innerHTML = gamestate [ "colony" ] ;
2025-08-07 14:10:20 -07:00
page _elements [ "lbl_shinies" ] . innerHTML = gamestate [ "shinies" ] . toFixed ( 2 ) ;
page _elements [ "lbl_food" ] . innerHTML = gamestate [ "food" ] . toFixed ( 2 ) ;
2025-08-05 18:09:48 -07:00
page _elements [ "lbl_class" ] . innerHTML = gamestate [ "class" ] ;
2025-07-31 11:28:09 -07:00
page _elements [ "lbl_xp" ] . innerHTML = gamestate [ "xp" ] ;
page _elements [ "lbl_xp_next" ] . innerHTML = gamestate [ "xp_next" ] ;
2025-08-07 15:37:23 -07:00
page _elements [ "lbl_level" ] . innerHTML = gamestate [ "level" ] ;
page _elements [ "menu_enc_human" ] . value = gamestate [ "enc_human" ] ;
page _elements [ "menu_enc_seagull" ] . value = gamestate [ "enc_seagull" ] ;
2023-02-01 14:52:56 -08:00
}
2025-07-29 12:50:35 -07:00
var dev _toolbox _open = false ;
function dev _toolbox ( open ) {
if ( open != dev _toolbox _open ) {
if ( open ) {
var div _toolbox = document . createElement ( "div" ) ;
page _elements [ "div_toolbox" ] = div _toolbox ;
div _toolbox . setAttribute ( "id" , "dev_toolbox" ) ;
fetch ( "/dev/get-toolbox" )
. then ( ( response ) => response . text ( ) )
. then ( ( resp ) => { div _toolbox . innerHTML = resp } )
page _elements [ "div_sidebar" ] . appendChild ( div _toolbox ) ;
}
else {
var div _toolbox = page _elements [ "div_toolbox" ] ;
page _elements [ "div_sidebar" ] . removeChild ( div _toolbox ) ;
2025-08-05 18:09:48 -07:00
div _toolbox . remove ( ) ;
2025-07-29 12:50:35 -07:00
delete page _elements [ "div_toolbox" ] ;
}
}
dev _toolbox _open = open ;
}
2023-02-01 14:52:56 -08:00
2025-08-07 14:10:20 -07:00
function reward _xp ( amount ) {
gamestate [ "xp" ] += amount ;
if ( gamestate [ "xp" ] >= gamestate [ "xp_next" ] ) {
old _xp _next = gamestate [ "xp_next" ] ;
gamestate [ "xp" ] -= old _xp _next ;
gamestate [ "level" ] += 1 ;
gamestate [ "xp_next" ] = ( old _xp _next * 1.5 ) + ( gamestate [ "level" ] * 5 ) ;
2025-09-02 16:44:28 -07:00
if ( gamestate [ "level" ] == 2 ) {
gamestate [ "story_beat" ] = 1 ;
record _log ( "The humans have fired off some sort of large rocket from a nearby platform. You watch it as it pierces the sky above you and fades into the heavens." ) ;
} else if ( gamestate [ "level" ] == 3 ) {
gamestate [ "story_beat" ] = 2 ;
gamestate [ "class" ] = "Seagull" ;
record _log ( "You have grown up from a young, eager seaglet to a full blown Seagull. As your colony participates in the ritual honoring your coming of age, you begin to detect a shift in the winds, though you're not certain exactly how." ) ;
}
2025-08-07 14:10:20 -07:00
}
}
2025-08-07 15:37:23 -07:00
async function steal _resource ( resource , target , amount , itemstr ) {
2025-08-07 14:10:20 -07:00
var items = itemstr . split ( "," )
2025-08-07 15:37:23 -07:00
var stealdata = await fetch ( ` /act/steal/ ${ resource } / ${ target } ` , { method : "POST" , body : JSON . stringify ( { gamestate : gamestate } ) } )
2025-08-05 17:57:43 -07:00
. then ( res => { return res . json ( ) ; } )
. catch ( e => { throw e ; } ) ;
2025-08-05 18:00:26 -07:00
if ( stealdata [ "success" ] && amount > 0 ) {
2025-08-05 17:57:43 -07:00
gamestate [ resource ] += amount ;
2025-08-07 14:10:20 -07:00
reward _xp ( 2 ) ;
2025-08-07 15:37:23 -07:00
record _log ( ` Stole ${ resource } from a ${ target } : ${ items . join ( ", " ) } ` ) ;
2025-08-05 17:57:43 -07:00
}
2025-08-07 15:37:23 -07:00
else { record _log ( ` Didn't steal ${ resource } from a ${ target } ` ) ; }
}
async function recruit ( amount ) {
2025-09-02 16:44:28 -07:00
if ( gamestate [ "shinies" ] < amount ) {
record _log ( "You do not have enough shinies to recruit this seagull." ) ;
return ;
}
2025-08-07 15:37:23 -07:00
var stealdata = await fetch ( "/act/recruit" , { method : "POST" , body : JSON . stringify ( { gamestate : gamestate } ) } )
. then ( res => { return res . json ( ) ; } )
. catch ( e => { throw e ; } ) ;
if ( stealdata [ "success" ] && amount > 0 ) {
gamestate [ "shinies" ] -= amount ;
reward _xp ( 5 ) ;
gamestate [ "colony" ] += 1 ;
record _log ( "Successfully recruited a seagull into the colony" ) ;
}
else { record _log ( "The other gull wasn't impressed. Recruiting failed." ) ; }
2025-08-05 17:57:43 -07:00
}
2023-02-01 14:52:56 -08:00
async function game _tick ( ) {
gamestate [ "tick" ] += 1 ;
2023-02-24 12:39:18 -08:00
ticks _since _last _save += 1 ;
2023-02-01 14:52:56 -08:00
page _elements [ "lbl_tick" ] . innerHTML = gamestate [ "tick" ] ;
var tickdata = await fetch ( "/tick" )
. then ( res => {
json = res . json ( )
console . log ( json )
return json
} )
. catch ( e => { throw e ; } ) ;
console . log ( JSON . stringify ( tickdata ) ) ;
if ( tickdata [ "code" ] != 200 ) {
console . error ( "Non-200 tick code: " + tickdata [ "code" ] ) ;
return ;
}
if ( tickdata [ "event_type" ] == 0 ) {
2023-02-27 20:22:36 -08:00
// pass
2023-02-01 14:52:56 -08:00
} else if ( tickdata [ "event_type" ] == 1 ) {
// Flavor event - no gameplay effect, but occasionally says something fun.
record _log ( tickdata [ "log" ] ) ;
2025-07-31 11:28:09 -07:00
} else if ( tickdata [ "event_type" ] == 10 ) { // ENCHUMAN
2025-08-05 17:57:43 -07:00
var total _food = 0 ;
var food _descs = [ ] ;
var total _shinies = 0 ;
var shinies _descs = [ ] ;
switch ( page _elements [ "menu_enc_human" ] . value ) {
case "pause" :
tickdata . items . food . forEach ( ( item ) => {
total _food += item [ "amount" ] ;
food _descs . push ( item [ "desc" ] ) ;
2025-08-07 14:10:20 -07:00
} ) ;
2025-08-05 17:57:43 -07:00
tickdata . items . shinies . forEach ( ( item ) => {
total _shinies += item [ "amount" ] ;
shinies _descs . push ( item [ "desc" ] ) ;
2025-08-07 14:10:20 -07:00
} ) ;
2025-08-05 17:57:43 -07:00
var logstring = "You have encountered a human. It is carrying these resources:\n\n"
logstring += "<ol>\n"
if ( total _food > 0 ) {
2025-08-07 14:10:20 -07:00
logstring += ` <li><b> ${ total _food . toFixed ( 2 ) } food:</b> ${ food _descs . join ( ", " ) } </li> \n ` ;
2025-08-05 17:57:43 -07:00
}
if ( total _shinies > 0 ) {
2025-08-07 14:10:20 -07:00
logstring += ` <li><b> ${ total _shinies . toFixed ( 2 ) } shinies:</b> ${ shinies _descs . join ( ", " ) } </li> \n ` ;
2025-08-05 17:57:43 -07:00
}
logstring += "</ol>\nWhat would you like to do?" ;
record _log _with _choices ( logstring ,
2025-08-07 15:37:23 -07:00
"Steal food" , ` steal_resource('food', 'humans', ${ total _food } , " ${ food _descs . toString ( ) } ") ` ,
"Steal shinies" , ` steal_resource('shinies', 'humans', ${ total _shinies } , " ${ shinies _descs . toString ( ) } ") `
2025-08-05 17:57:43 -07:00
)
break ;
case "steal-food" :
2025-08-07 15:37:23 -07:00
record _log ( "You have encountered a human. Attempting to steal food." ) ;
2025-08-05 17:57:43 -07:00
tickdata . items . food . forEach ( ( item ) => {
total _food += item [ "amount" ] ;
food _descs . push ( item [ "desc" ] ) ;
} )
2025-08-07 15:37:23 -07:00
steal _resource ( "food" , "humans" , total _food , food _descs . toString ( ) ) ;
2025-08-05 17:57:43 -07:00
break ;
case "steal-shinies" :
2025-08-07 15:37:23 -07:00
record _log ( "You have encountered a human. Attempting to steal shinies." ) ;
2025-08-05 17:57:43 -07:00
tickdata . items . shinies . forEach ( ( item ) => {
total _shinies += item [ "amount" ] ;
shinies _descs . push ( item [ "desc" ] ) ;
} )
2025-08-07 15:37:23 -07:00
steal _resource ( "shinies" , "humans" , total _shinies , shinies _descs . toString ( ) ) ;
break ;
default :
console . error ( "undefined action " + page _elements [ "menu_enc_human" ] ) ;
break ;
}
} else if ( tickdata [ "event_type" ] == 11 ) { // ENCGULL
var total _food = 0 ;
var food _descs = [ ] ;
var total _shinies = 0 ;
var shinies _descs = [ ] ;
switch ( page _elements [ "menu_enc_seagull" ] . value ) {
case "pause" :
tickdata . items . food . forEach ( ( item ) => {
total _food += item [ "amount" ] ;
food _descs . push ( item [ "desc" ] ) ;
} ) ;
tickdata . items . shinies . forEach ( ( item ) => {
total _shinies += item [ "amount" ] ;
shinies _descs . push ( item [ "desc" ] ) ;
} ) ;
var logstring = "You have encountered a seagull. It is carrying these resources:\n\n"
logstring += "<ol>\n"
if ( total _food > 0 ) {
logstring += ` <li><b> ${ total _food . toFixed ( 2 ) } food:</b> ${ food _descs . join ( ", " ) } </li> \n ` ;
}
if ( total _shinies > 0 ) {
logstring += ` <li><b> ${ total _shinies . toFixed ( 2 ) } shinies:</b> ${ shinies _descs . join ( ", " ) } </li> \n ` ;
}
logstring += "</ol>\nWhat would you like to do?" ;
record _log _with _choices ( logstring ,
"Recruit" , ` recruit( ${ tickdata . recruit _cost } ) ` ,
"Steal food" , ` steal_resource('food', 'seagulls', ${ total _food } , " ${ food _descs . toString ( ) } ") ` ,
"Steal shinies" , ` steal_resource('shinies', 'seagulls', ${ total _shinies } , " ${ shinies _descs . toString ( ) } ") `
)
break ;
case "recruit" :
recruit ( tickdata . recruit _cost ) ;
break
case "steal-food" :
record _log ( "You have encountered a seagull. Attempting to steal food." ) ;
tickdata . items . food . forEach ( ( item ) => {
total _food += item [ "amount" ] ;
food _descs . push ( item [ "desc" ] ) ;
} )
steal _resource ( "food" , "seagulls" , total _food , food _descs . toString ( ) ) ;
break ;
case "steal-shinies" :
record _log ( "You have encountered a seagull. Attempting to steal shinies." ) ;
tickdata . items . shinies . forEach ( ( item ) => {
total _shinies += item [ "amount" ] ;
shinies _descs . push ( item [ "desc" ] ) ;
} )
steal _resource ( "shinies" , "seagulls" , total _shinies , shinies _descs . toString ( ) ) ;
2025-08-05 17:57:43 -07:00
break ;
default :
console . error ( "undefined action " + page _elements [ "menu_enc_human" ] ) ;
break ;
}
2023-02-27 20:22:36 -08:00
}
2023-02-24 12:39:18 -08:00
// sanity check
if ( ! ( "autosave" in gamestate ) ) {
gamestate [ "autosave" ] = 35 ;
}
if ( ticks _since _last _save % gamestate [ "autosave" ] == 0 && ticks _since _last _save != 0 ) {
save _game ( ) ;
ticks _since _last _save = 0 ;
}
2025-07-29 12:50:35 -07:00
update _ui ( ) ;
}
var start _event = "" ;
var target = null ;
if ( desktop _mode ) {
// pywebview's native JS is nerfed in a few places and needs the additional python API
// which gets loaded after initial DOM via injections
start _event = "pywebviewready" ;
target = window ;
2023-02-01 14:52:56 -08:00
}
2025-07-29 12:50:35 -07:00
else {
2025-08-05 18:09:48 -07:00
// in web mode, browsers are expected to have working local storage by this point
2025-07-29 12:50:35 -07:00
start _event = "DOMContentLoaded" ;
target = document ;
}
2025-08-07 15:37:23 -07:00
function update _action ( enc , value ) {
gamestate [ ` enc_ ${ enc } ` ] = value ;
}
2023-02-01 14:52:56 -08:00
2025-07-29 12:50:35 -07:00
target . addEventListener ( start _event , function ( ev ) {
2023-01-28 21:08:47 -08:00
page _elements [ "div_log" ] = document . querySelector ( "#main-log" ) ;
2025-07-29 12:50:35 -07:00
page _elements [ "div_sidebar" ] = document . querySelector ( "#main-sidebar" ) ;
2023-01-28 21:08:47 -08:00
page _elements [ "div_name" ] = document . querySelector ( "#side-seagull-name" ) ;
page _elements [ "div_name_editor" ] = document . querySelector ( "#side-seagull-name-editor" ) ;
page _elements [ "lbl_name" ] = document . querySelector ( "#lbl-seagull-name" ) ;
2025-07-31 11:28:09 -07:00
page _elements [ "lbl_class" ] = document . querySelector ( "#lbl-seagull-class" ) ;
2023-02-24 12:39:18 -08:00
page _elements [ "lbl_colony" ] = document . querySelector ( "#lbl-seagull-colony" ) ;
2025-07-29 12:50:35 -07:00
page _elements [ "lbl_shinies" ] = document . querySelector ( "#lbl-seagull-shinies" ) ;
page _elements [ "lbl_food" ] = document . querySelector ( "#lbl-seagull-food" ) ;
2023-01-28 21:08:47 -08:00
page _elements [ "edt_name" ] = document . querySelector ( "#edt-seagull-name" ) ;
2023-02-01 14:52:56 -08:00
page _elements [ "lbl_tick" ] = document . querySelector ( "#main-day-counter" ) ;
2025-07-29 12:50:35 -07:00
page _elements [ "lbl_xp" ] = document . querySelector ( "#lbl-seagull-xp-current" ) ;
page _elements [ "lbl_xp_next" ] = document . querySelector ( "#lbl-seagull-xp-next" ) ;
2025-08-07 15:37:23 -07:00
page _elements [ "lbl_level" ] = document . querySelector ( "#lbl-seagull-lvl" ) ;
2025-07-31 11:28:09 -07:00
page _elements [ "menu_enc_human" ] = document . querySelector ( "#menu-enc-human" ) ;
page _elements [ "menu_enc_seagull" ] = document . querySelector ( "#menu-enc-seagull" ) ;
2025-09-02 16:44:28 -07:00
page _elements [ "btn_charsheet" ] = document . querySelector ( "#button-charsheet" ) ;
page _elements [ "btn_settings" ] = document . querySelector ( "#button-settings" ) ;
page _elements [ "btn_about" ] = document . querySelector ( "#button-about" ) ;
2023-02-01 14:52:56 -08:00
2025-09-02 16:44:28 -07:00
page _elements [ "menu_enc_human" ] . addEventListener ( "change" , ( ev ) => { update _action ( "human" , ev . target . value ) } ) ;
page _elements [ "menu_enc_seagull" ] . addEventListener ( "change" , ( ev ) => { update _action ( "seagull" , ev . target . value ) } ) ;
page _elements [ "btn_charsheet" ] . addEventListener ( "click" , ( ev ) => { open _modal _dialog ( "charsheet" ) } ) ;
2025-08-07 15:37:23 -07:00
2025-08-07 14:10:20 -07:00
prepare _gamestate ( ) ;
2023-01-28 21:08:47 -08:00
2023-02-27 17:12:27 -08:00
record _log ( "seagull game ver. " + ver _string ) ;
2023-02-01 14:52:56 -08:00
const interval = setInterval ( ( ) => {
if ( tick _meter _running ) { game _tick ( ) ; }
} , 1200 ) ;
2025-08-07 14:10:20 -07:00
update _ui ( ) ;
2023-01-28 21:08:47 -08:00
} ) ;
function change _seagull _name ( ) {
page _elements [ "div_name" ] . style . display = "none" ;
page _elements [ "div_name_editor" ] . style . display = "block" ;
}
function confirm _seagull _name ( ) {
const new _name = page _elements [ "edt_name" ] . value ;
page _elements [ "lbl_name" ] . innerHTML = new _name ;
2023-02-01 14:52:56 -08:00
gamestate [ "name" ] = new _name ;
save _game ( ) ;
2023-01-28 21:08:47 -08:00
page _elements [ "div_name" ] . style . display = "block" ;
page _elements [ "div_name_editor" ] . style . display = "none" ;
}
function cancel _seagull _name ( ) {
page _elements [ "edt_name" ] . value = "" ;
page _elements [ "div_name" ] . style . display = "block" ;
page _elements [ "div_name_editor" ] . style . display = "none" ;
}