From db6a75f4743ebf89ebadcc6b6768f28abf63f10d Mon Sep 17 00:00:00 2001 From: Mike Caron Date: Mon, 25 Oct 2021 23:58:49 -0400 Subject: [PATCH] Add support for Steam achievements This is a complicated subject, but this involves: - adding support for Steamworks - adding support for achievements to be defined - adding support at runtime to detect when achievement conditions have been met There is also a test game that adds support for achievements in Steam's test game --- .gitignore | 6 + SConscript | 5 +- achievements.bi | 41 +++ achievements.rbas | 177 +++++++++++ achievements_runtime.bi | 29 ++ achievements_runtime.rbas | 300 ++++++++++++++++++ allmodex.bas | 2 + custom.bas | 11 +- custom.bi | 1 - customsubs.rbas | 6 +- game.bas | 21 +- moresubs.bi | 1 + moresubs.rbas | 3 + reload.bas | 12 + reload.bi | 1 + savegame.rbas | 5 + steam.bas | 244 ++++++++++++++ steam.bi | 20 ++ steam_internal.bi | 44 +++ testgame/achievements.hss | 21 ++ .../achievements.rpgdir/achievements.reld | Bin 0 -> 414 bytes testgame/achievements.rpgdir/achievements.xml | 34 ++ testgame/achievements.rpgdir/archinym.lmp | 2 + testgame/achievements.rpgdir/attack.bin | Bin 0 -> 616 bytes testgame/achievements.rpgdir/attacks.rgfx | Bin 0 -> 7744 bytes testgame/achievements.rpgdir/backdrops.rgfx | Bin 0 -> 64184 bytes testgame/achievements.rpgdir/binsize.bin | Bin 0 -> 26 bytes testgame/achievements.rpgdir/boxborders.rgfx | Bin 0 -> 4652 bytes testgame/achievements.rpgdir/browse.txt | Bin 0 -> 80 bytes testgame/achievements.rpgdir/defpal0.bin | Bin 0 -> 2 bytes testgame/achievements.rpgdir/defpal1.bin | Bin 0 -> 2 bytes testgame/achievements.rpgdir/defpal2.bin | Bin 0 -> 2 bytes testgame/achievements.rpgdir/defpal3.bin | Bin 0 -> 2 bytes testgame/achievements.rpgdir/defpal4.bin | Bin 0 -> 10 bytes testgame/achievements.rpgdir/defpal5.bin | Bin 0 -> 2 bytes testgame/achievements.rpgdir/defpal6.bin | Bin 0 -> 2 bytes testgame/achievements.rpgdir/defpal7.bin | Bin 0 -> 2 bytes testgame/achievements.rpgdir/defpal8.bin | Bin 0 -> 2 bytes testgame/achievements.rpgdir/defpass.bin | Bin 0 -> 322 bytes testgame/achievements.rpgdir/enemies.rgfx | Bin 0 -> 10566 bytes testgame/achievements.rpgdir/fixbits.bin | 1 + testgame/achievements.rpgdir/general.reld | Bin 0 -> 2768 bytes testgame/achievements.rpgdir/globalnpcs1.n | Bin 0 -> 45 bytes testgame/achievements.rpgdir/heroes.reld | Bin 0 -> 2073 bytes testgame/achievements.rpgdir/heroes.rgfx | Bin 0 -> 10608 bytes testgame/achievements.rpgdir/lookup1.bin | Bin 0 -> 80 bytes testgame/achievements.rpgdir/menuitem.bin | Bin 0 -> 816 bytes testgame/achievements.rpgdir/menus.bin | Bin 0 -> 56 bytes testgame/achievements.rpgdir/ohrrpgce.d00 | Bin 0 -> 2007 bytes testgame/achievements.rpgdir/ohrrpgce.dox | Bin 0 -> 12000 bytes testgame/achievements.rpgdir/ohrrpgce.dt0 | Bin 0 -> 1716 bytes testgame/achievements.rpgdir/ohrrpgce.dt1 | Bin 0 -> 1488 bytes testgame/achievements.rpgdir/ohrrpgce.dt6 | Bin 0 -> 80 bytes testgame/achievements.rpgdir/ohrrpgce.e00 | Bin 0 -> 371 bytes testgame/achievements.rpgdir/ohrrpgce.efs | Bin 0 -> 100 bytes testgame/achievements.rpgdir/ohrrpgce.fnt | Bin 0 -> 2055 bytes testgame/achievements.rpgdir/ohrrpgce.for | Bin 0 -> 320 bytes testgame/achievements.rpgdir/ohrrpgce.gen | Bin 0 -> 1007 bytes testgame/achievements.rpgdir/ohrrpgce.hsp | Bin 0 -> 183893 bytes testgame/achievements.rpgdir/ohrrpgce.itm | Bin 0 -> 1440 bytes testgame/achievements.rpgdir/ohrrpgce.l00 | Bin 0 -> 3607 bytes testgame/achievements.rpgdir/ohrrpgce.map | Bin 0 -> 1520 bytes testgame/achievements.rpgdir/ohrrpgce.mas | Bin 0 -> 1543 bytes testgame/achievements.rpgdir/ohrrpgce.mn | Bin 0 -> 160 bytes testgame/achievements.rpgdir/ohrrpgce.n00 | Bin 0 -> 235 bytes testgame/achievements.rpgdir/ohrrpgce.p00 | Bin 0 -> 371 bytes testgame/achievements.rpgdir/ohrrpgce.pal | Bin 0 -> 176 bytes testgame/achievements.rpgdir/ohrrpgce.say | Bin 0 -> 4220 bytes testgame/achievements.rpgdir/ohrrpgce.sho | Bin 0 -> 40 bytes testgame/achievements.rpgdir/ohrrpgce.sng | 100 ++++++ testgame/achievements.rpgdir/ohrrpgce.stf | Bin 0 -> 84 bytes testgame/achievements.rpgdir/ohrrpgce.stt | Bin 0 -> 3595 bytes testgame/achievements.rpgdir/ohrrpgce.t00 | Bin 0 -> 371 bytes testgame/achievements.rpgdir/ohrrpgce.tap | Bin 0 -> 80 bytes testgame/achievements.rpgdir/ohrrpgce.til | Bin 0 -> 64000 bytes testgame/achievements.rpgdir/ohrrpgce.tmn | Bin 0 -> 294 bytes testgame/achievements.rpgdir/ohrrpgce.veh | Bin 0 -> 240 bytes testgame/achievements.rpgdir/ohrrpgce.z00 | Bin 0 -> 316 bytes testgame/achievements.rpgdir/palettes.bin | Bin 0 -> 772 bytes testgame/achievements.rpgdir/plotscr.lst | Bin 0 -> 7720 bytes testgame/achievements.rpgdir/portraits.rgfx | Bin 0 -> 2696 bytes testgame/achievements.rpgdir/sfxdata.bin | Bin 0 -> 68 bytes testgame/achievements.rpgdir/songdata.bin | Bin 0 -> 128 bytes testgame/achievements.rpgdir/uicolors.bin | Bin 0 -> 126 bytes testgame/achievements.rpgdir/walkabouts.rgfx | Bin 0 -> 21372 bytes testgame/achievements.rpgdir/weapons.rgfx | Bin 0 -> 1372 bytes util.bas | 9 + util.bi | 2 + yetmore2.bas | 9 + 89 files changed, 1088 insertions(+), 19 deletions(-) create mode 100644 achievements.bi create mode 100644 achievements.rbas create mode 100644 achievements_runtime.bi create mode 100644 achievements_runtime.rbas create mode 100644 steam.bas create mode 100644 steam.bi create mode 100644 steam_internal.bi create mode 100644 testgame/achievements.hss create mode 100644 testgame/achievements.rpgdir/achievements.reld create mode 100644 testgame/achievements.rpgdir/achievements.xml create mode 100644 testgame/achievements.rpgdir/archinym.lmp create mode 100644 testgame/achievements.rpgdir/attack.bin create mode 100644 testgame/achievements.rpgdir/attacks.rgfx create mode 100644 testgame/achievements.rpgdir/backdrops.rgfx create mode 100644 testgame/achievements.rpgdir/binsize.bin create mode 100644 testgame/achievements.rpgdir/boxborders.rgfx create mode 100644 testgame/achievements.rpgdir/browse.txt create mode 100644 testgame/achievements.rpgdir/defpal0.bin create mode 100644 testgame/achievements.rpgdir/defpal1.bin create mode 100644 testgame/achievements.rpgdir/defpal2.bin create mode 100644 testgame/achievements.rpgdir/defpal3.bin create mode 100644 testgame/achievements.rpgdir/defpal4.bin create mode 100644 testgame/achievements.rpgdir/defpal5.bin create mode 100644 testgame/achievements.rpgdir/defpal6.bin create mode 100644 testgame/achievements.rpgdir/defpal7.bin create mode 100644 testgame/achievements.rpgdir/defpal8.bin create mode 100644 testgame/achievements.rpgdir/defpass.bin create mode 100644 testgame/achievements.rpgdir/enemies.rgfx create mode 100644 testgame/achievements.rpgdir/fixbits.bin create mode 100644 testgame/achievements.rpgdir/general.reld create mode 100644 testgame/achievements.rpgdir/globalnpcs1.n create mode 100644 testgame/achievements.rpgdir/heroes.reld create mode 100644 testgame/achievements.rpgdir/heroes.rgfx create mode 100644 testgame/achievements.rpgdir/lookup1.bin create mode 100644 testgame/achievements.rpgdir/menuitem.bin create mode 100644 testgame/achievements.rpgdir/menus.bin create mode 100644 testgame/achievements.rpgdir/ohrrpgce.d00 create mode 100644 testgame/achievements.rpgdir/ohrrpgce.dox create mode 100644 testgame/achievements.rpgdir/ohrrpgce.dt0 create mode 100644 testgame/achievements.rpgdir/ohrrpgce.dt1 create mode 100644 testgame/achievements.rpgdir/ohrrpgce.dt6 create mode 100644 testgame/achievements.rpgdir/ohrrpgce.e00 create mode 100644 testgame/achievements.rpgdir/ohrrpgce.efs create mode 100644 testgame/achievements.rpgdir/ohrrpgce.fnt create mode 100644 testgame/achievements.rpgdir/ohrrpgce.for create mode 100644 testgame/achievements.rpgdir/ohrrpgce.gen create mode 100644 testgame/achievements.rpgdir/ohrrpgce.hsp create mode 100644 testgame/achievements.rpgdir/ohrrpgce.itm create mode 100644 testgame/achievements.rpgdir/ohrrpgce.l00 create mode 100644 testgame/achievements.rpgdir/ohrrpgce.map create mode 100644 testgame/achievements.rpgdir/ohrrpgce.mas create mode 100644 testgame/achievements.rpgdir/ohrrpgce.mn create mode 100644 testgame/achievements.rpgdir/ohrrpgce.n00 create mode 100644 testgame/achievements.rpgdir/ohrrpgce.p00 create mode 100644 testgame/achievements.rpgdir/ohrrpgce.pal create mode 100644 testgame/achievements.rpgdir/ohrrpgce.say create mode 100644 testgame/achievements.rpgdir/ohrrpgce.sho create mode 100644 testgame/achievements.rpgdir/ohrrpgce.sng create mode 100644 testgame/achievements.rpgdir/ohrrpgce.stf create mode 100644 testgame/achievements.rpgdir/ohrrpgce.stt create mode 100644 testgame/achievements.rpgdir/ohrrpgce.t00 create mode 100644 testgame/achievements.rpgdir/ohrrpgce.tap create mode 100644 testgame/achievements.rpgdir/ohrrpgce.til create mode 100644 testgame/achievements.rpgdir/ohrrpgce.tmn create mode 100644 testgame/achievements.rpgdir/ohrrpgce.veh create mode 100644 testgame/achievements.rpgdir/ohrrpgce.z00 create mode 100644 testgame/achievements.rpgdir/palettes.bin create mode 100644 testgame/achievements.rpgdir/plotscr.lst create mode 100644 testgame/achievements.rpgdir/portraits.rgfx create mode 100644 testgame/achievements.rpgdir/sfxdata.bin create mode 100644 testgame/achievements.rpgdir/songdata.bin create mode 100644 testgame/achievements.rpgdir/uicolors.bin create mode 100644 testgame/achievements.rpgdir/walkabouts.rgfx create mode 100644 testgame/achievements.rpgdir/weapons.rgfx diff --git a/.gitignore b/.gitignore index 0aae8893b..47da6aac1 100644 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,9 @@ ohr.geany *.jpeg # Don't ignore anything under data/ !data/** + +#steam +steam_appid.txt +steam_api.dll +libsteam_api.so +steamclient.so diff --git a/SConscript b/SConscript index 08a26adc3..854ed61a2 100644 --- a/SConscript +++ b/SConscript @@ -1112,7 +1112,8 @@ reload_modules = ['reload.bas', # (.bas files only) # (All shared FB files are here instead of common_modules so we don't have to # remember where using IS_GAME/IS_CUSTOM is allowed.) -shared_modules += ['allmodex', +shared_modules += ['achievements.rbas', + 'allmodex', 'audiofile', 'backends', 'bam2mid', @@ -1127,6 +1128,7 @@ shared_modules += ['allmodex', 'reloadext', 'sliceedit', 'slices', + 'steam', 'thingbrowser', 'plankmenu'] @@ -1157,6 +1159,7 @@ edit_modules = ['custom', # (.bas files only) game_modules = ['game', + 'achievements_runtime.rbas', 'bmod.rbas', 'bmodsubs', 'menustuf.rbas', diff --git a/achievements.bi b/achievements.bi new file mode 100644 index 000000000..04302519a --- /dev/null +++ b/achievements.bi @@ -0,0 +1,41 @@ +'OHRRPGCE GAME - achievement definitions +'(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and Hamster Republic Productions +'Please read LICENSE.txt for GPL License details and disclaimer of liability +' +#ifndef ACHIEVEMENTS_BI +#define ACHIEVEMENTS_BI + +#include "common_base.bi" + +namespace Achievements + + enum AchievementType + flag + count + end enum + + type AchievementDefinition + id as uinteger = 0 + name as string = "" + achievement_type as AchievementType = AchievementType.flag + max_value as uint64 = 0 + progress_interval as uinteger = 0 + latching as boolean = false + tags as integer vector ' yay, free basic + steam_id as string = "" + + declare constructor + declare destructor + end type + + declare sub definitions_load(file_path as string) + declare sub definitions_free() + declare sub definitions_reset() + declare function definitions_count() as integer + declare function get_definition_by_index(index as integer) byref as AchievementDefinition + declare function get_definition_by_id(id as integer) byref as AchievementDefinition + declare function is_permanent() as boolean + +end namespace + +#endif diff --git a/achievements.rbas b/achievements.rbas new file mode 100644 index 000000000..3949cf973 --- /dev/null +++ b/achievements.rbas @@ -0,0 +1,177 @@ +'OHRRPGCE GAME - achievement definitions +'(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and Hamster Republic Productions +'Please read LICENSE.txt for GPL License details and disclaimer of liability +' +#include "achievements.bi" +#include "reload.bi" +#include "steam.bi" + +' uncomment to enable debug output +' #define DEBUG_ACHIEVEMENTS + +using Reload + +namespace Achievements + + ' Local functions + declare function type_name_to_enum(name as string) as AchievementType + + #ifdef DEBUG_ACHIEVEMENTS + declare sub ach_debug(msg as string) + #else + #define ach_debug(x) + #endif + + dim shared next_id as integer = 0 + dim shared permanent as boolean = false + dim shared reset_queued as boolean = false + dim shared achievement_defs() as AchievementDefinition + + ' -- public api -- + + sub definitions_load(file_path as string) + definitions_free + + DIM doc as Docptr + doc = LoadDocument(file_path, LoadOptions.optIgnoreMissing) + if doc = null then return + + dim cheevos as Nodeptr = DocumentRoot(doc) + if cheevos = null then + FreeDocument(doc) + return + end if + + dim num_achievements as integer = CountChildren(cheevos, "achievement") + + ach_debug("loading " & num_achievements & " from disk") + + if num_achievements = 0 then + erase achievement_defs + else + redim achievement_defs(num_achievements - 1) + end if + + dim i as integer = 0 + + READNODE cheevos + next_id = cheevos."next_id".integer + permanent = cheevos."permanent".exists + + WITHNODE cheevos."achievement" as cheevo + ach_debug("loading achievement #" & i) + with achievement_defs(i) + .id = GetInteger(cheevo) + .name = cheevo."name".string + .achievement_type = type_name_to_enum(cheevo."type".string) + .max_value = cheevo."max_value".integer + .progress_interval = cheevo."progress_interval".integer + .latching = cheevo."latching".exists + .steam_id = cheevo."steam_id".string + + dim tag as Nodeptr = FirstChild(cheevo."tags".ptr, "tag") + while tag <> null + v_append .tags, GetInteger(tag) + tag = NextSibling(tag, "tag") + wend + end with + ach_debug("Finished loading achievement #" & i & ", " & achievement_defs(i).name) + i += 1 + END WITHNODE + END READNODE + + FreeDocument(doc) + + if reset_queued then + ach_debug("Resetting achievements (hopefully this is after the steam data stuff is loaded)") + reset_queued = false + + for i as integer = 0 to ubound(achievement_defs) + Steam.clear_achievement(achievement_defs(i).steam_id) + next + end if + end sub + + sub definitions_free + ach_debug("definitions_free") + erase achievement_defs + + next_id = 1 + permanent = false + end sub + + sub definitions_reset + ach_debug("definitions_reset") + + reset_queued = true + end sub + + function definitions_count() as integer + ach_debug("definitions_count") + return ubound(achievement_defs) - lbound(achievement_defs) + 1 + end function + + function get_definition_by_index(index as integer) byref as AchievementDefinition + ach_debug("get_definition_by_index(" & index & ")") + + ERROR_IF(index < 0 orelse index > ubound(achievement_defs), "Invalid achievement index, " & index, byval null) + + return achievement_defs(index) + end function + + function get_definition_by_id(id as integer) byref as AchievementDefinition + for i as integer = 0 to ubound(achievement_defs) + if achievement_defs(i).id = id then return achievement_defs(i) + next + ERROR_IF(true, "Invalid achievement id, " & id, byval null) + end function + + function is_permanent() as boolean + return permanent + end function + + ' -- members for AchievementDefinition -- + + constructor AchievementDefinition + ach_debug("AchivementProgress constructor") + v_new tags + end constructor + + destructor AchievementDefinition + ach_debug("AchivementDefinition destructor") + v_free tags + end destructor + + ' -- internal helpers -- + + function type_name_to_enum(name as string) as AchievementType + select case name + case "flag" + return AchievementType.flag + case "count" + return AchievementType.count + case else + ach_debug("unknown achievement type string: " & name) + return AchievementType.flag + end select + end function + + function type_enum_to_name(typ as AchievementType) as string + select case typ + case AchievementType.flag + return "flag" + case AchievementType.count + return "count" + case else + ach_debug("unknown achievement type enum: " & typ) + return "flag" + end select + end function + + #ifdef DEBUG_ACHIEVEMENTS + private sub ach_debug(msg as string) + debug "achievements: " & msg + end sub + #endif + +end namespace diff --git a/achievements_runtime.bi b/achievements_runtime.bi new file mode 100644 index 000000000..b58a78fa7 --- /dev/null +++ b/achievements_runtime.bi @@ -0,0 +1,29 @@ +'OHRRPGCE GAME - in-game support for achievements +'(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and Hamster Republic Productions +'Please read LICENSE.txt for GPL License details and disclaimer of liability +' +#ifndef ACHIEVEMENTS_RUNTIME_BI +#define ACHIEVEMENTS_RUNTIME_BI + +#include "reload.bi" + +namespace Achievements + +type AchievementProgress + id as uinteger = 0 + value as uint64 = 0 + seen_tags as integer vector + rewarded as boolean + rewarded_date as double + + declare constructor + declare destructor +end type + +declare sub evaluate_tags() +declare sub runtime_reset() ' clears data, for a new game +declare sub runtime_load(node as Reload.NodePtr) +declare sub runtime_save(node as Reload.NodePtr) + +end namespace +#endif diff --git a/achievements_runtime.rbas b/achievements_runtime.rbas new file mode 100644 index 000000000..e82d31550 --- /dev/null +++ b/achievements_runtime.rbas @@ -0,0 +1,300 @@ +'OHRRPGCE GAME - in-game support for achievements +'(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and Hamster Republic Productions +'Please read LICENSE.txt for GPL License details and disclaimer of liability +' +#include "achievements_runtime.bi" +#include "achievements.bi" +#include "moresubs.bi" +#include "purchase.bi" +#include "steam.bi" +#include "vbcompat.bi" 'for NOW + +' -- achievements_runtime +' This file contains functionality related to implementing achievements during gameplay. This includes +' keeping track of seen tags, progress values, etc, as well as logic to reward achievements if +' appropriate. +' +' Assumptions: +' * achievement definitions (achievements.rbas) have been loaded +' * the indexes in achievement_progress match the indexes in the achievement definitions +' +' IDs (in the type-member sense) are _not_ used to match achievements. These IDs are only for save data + + +' uncomment to enable spammy debug output +' #define DEBUG_ACHIEVEMENTS + +using Reload + +namespace Achievements + + declare function mark_tag_as_seen(index as integer, tag as integer) as boolean + declare function needs_rewarding(index as integer) as boolean + declare function needs_progress_update(index as integer) as boolean + declare sub reward_achievement(index as integer) + + #ifdef DEBUG_ACHIEVEMENTS + declare sub ach_debug(msg as string) ' this is actually defined in achievements.rbas + #else + #define ach_debug(x) + #endif + + + ' -- globals -- + dim shared achievement_progress() as AchievementProgress + dim shared save_loaded as boolean = false + + ' -- public api -- + + sub evaluate_tags() + ach_debug("achievements_evaluate_tags") + + if save_loaded = false then + ach_debug("save is not loaded, aborting evaluation") + return 'don't evaluate anything before the save has actually been loaded + end if + + for index as integer = 0 to ubound(achievement_progress) + ach_debug("evaluating #" & index) + + if achievement_progress(index).rewarded then continue for ' ignore rewarded achievements + + with get_definition_by_index(index) + ach_debug("evaluating '" & .name & "'") + for t as integer = 0 to v_len(.tags) - 1 + dim tag as integer = .tags[t] + + if mark_tag_as_seen(index, tag) then + if needs_rewarding(index) then + ' reward the achievement + reward_achievement index + elseif needs_progress_update(index) then + ' ping user with progress + ach_debug("notifying progress on '" & .name & "'") + Steam.notify_achievement_progress .steam_id, achievement_progress(index).value, .max_value + end if + end if + next + end with + next + end sub + + sub runtime_reset + dim count as integer = definitions_count() + ach_debug("resetting achievement data to " & count) + + if count = 0 then + erase achievement_progress + else + redim achievement_progress(count - 1) + end if + + save_loaded = false + end sub + + sub runtime_load(node as Reload.NodePtr) + runtime_reset + + ach_debug("loading save game") + + for index as integer = 0 to ubound(achievement_progress) + achievement_progress(index).id = get_definition_by_index(index).id + next + + if node <> null then + READNODE node + WITHNODE node."achievement" as achievement + dim progress as AchievementProgress ptr = null + dim id as integer = GetInteger(achievement) + + for index as integer = 0 to ubound(achievement_progress) + if achievement_progress(index).id = id then + ach_debug("progress in slot " & index & " has index " & id) + progress = @achievement_progress(index) + exit for + else + ach_debug("progress in slot " & index & " has wrong index") + end if + next + + if progress <> null then + progress->value = achievement."value".integer + progress->rewarded = achievement."rewarded".exists + progress->rewarded_date = achievement."rewarded_date".double + + ach_debug("loaded progress of " & progress->value & " for id " & id) + ach_debug("loaded rewarded state of " & progress->rewarded & " for id " & id) + dim tag as Nodeptr = FirstChild(achievement."tags".ptr, "tag") + while tag <> null + dim t as integer = GetInteger(tag) + ach_debug("noted tag " & t & " for id " & id) + v_append progress->seen_tags, t + tag = NextSibling(tag, "tag") + wend + else + ach_debug("Encountered data for missing achievement with id " & id & ". Discarding") + end if + END WITHNODE + END READNODE + end if + + ach_debug("done loading save game") + save_loaded = true + end sub + + sub runtime_save(node as NodePtr) + ach_debug("saving achievement data") + + if ubound(achievement_progress) < 0 then + ach_debug("no achievements to save") + return + end if + + dim achievements_node as NodePtr = AppendChildNode(node, "achievements") + + for index as integer = 0 to ubound(achievement_progress) + with achievement_progress(index) + ach_debug("State of achievement id " & .id & ": Value=" & .value & ", tags=" & v_len(.seen_tags)) + + if .value <= 0 andalso .rewarded = false andalso v_len(.seen_tags) <= 0 then continue for + + dim achievement as NodePtr = AppendChildNode(achievements_node, "achievement", .id) + AppendChildNode(achievement, "value", .value) + if .rewarded then + SetChildNodeDate(achievement, "rewarded", NOW()) + end if + + dim tags as NodePtr = AppendChildNode(achievement, "tags") + for t as integer = 0 to v_len(.seen_tags) - 1 + AppendChildNode(tags, "tag", .seen_tags[t]) + next + end with + next + end sub + + ' -- members for AchievementProgress -- + + constructor AchievementProgress + ach_debug("AchivementProgress constructor") + v_new seen_tags + end constructor + + destructor AchievementProgress + ach_debug("AchivementProgress destructor") + v_free seen_tags + end destructor + + ' -- internal functions -- + + private function mark_tag_as_seen(index as integer, tag as integer) as boolean + ach_debug("mark_tag_as_seen") + + if achievement_progress(index).rewarded then return false ' ignore rewarded achievements + + with get_definition_by_index(index) + dim is_triggered as boolean = istag(tag) + dim ix as integer = v_find(achievement_progress(index).seen_tags, tag) + ach_debug("Relevant tag " & tag & " is " & is_triggered) + if is_triggered and ix = -1 then + ach_debug("This means that is is now on!") + v_append achievement_progress(index).seen_tags, tag + if .achievement_type = AchievementType.count and achievement_progress(index).value < .max_value then + achievement_progress(index).value += 1 + end if + return true + elseif .latching = false andalso is_triggered = false andalso ix <> -1 then + ach_debug("This means that is is now off!") + v_remove achievement_progress(index).seen_tags, tag + ' note: disabling a tag does _not_ decrement the value + ' for this reason, we return false since the achievement state didn't really change + return false + end if + end with + + return false + end function + + private function needs_rewarding(index as integer) as boolean + ach_debug("needs_rewarding") + dim byref achievement as AchievementDefinition = get_definition_by_index(index) + dim byref progress as AchievementProgress = achievement_progress(index) + + if progress.rewarded then return false ' ignore rewarded achievements + + select case achievement.achievement_type + case AchievementType.flag + for ix as integer = 0 to v_len(achievement.tags) - 1 + dim tag as integer = achievement.tags[ix] + if v_find(progress.seen_tags, tag) = -1 then + ach_debug(":( '" & achievement.name & "' is not complete because tag " & tag & " is not set") + return false + end if + next + case AchievementType.count + if progress.value < achievement.max_value then + ach_debug(":( '" & achievement.name & "' is not complete because value " & progress.value & " is less than " & achievement.max_value) + return false + end if + end select + ach_debug(":D '" & achievement.name & "' is complete!") + return true + end function + + ' call this _after_ checking is_complete! + private function needs_progress_update(index as integer) as boolean + ach_debug("needs_progress_update(" & index & ")") + dim byref achievement as AchievementDefinition = get_definition_by_index(index) + dim byref progress as AchievementProgress = achievement_progress(index) + + ach_debug((achievement.achievement_type = AchievementType.count) & _ + ", " & (achievement.progress_interval > 0) & _ + ", " & (progress.value > 0) & _ + ", " & (progress.value mod achievement.progress_interval) _ + ) + return achievement.achievement_type = AchievementType.count _ + andalso achievement.progress_interval > 0 _ + andalso progress.value > 0 _ + andalso (progress.value mod achievement.progress_interval) = 0 + end function + + private sub reward_achievement(index as integer) + dim needs_persist as boolean = false + + with get_definition_by_index(index) + if .steam_id <> "" then + ach_debug("rewarding '" & .name & "'") + Steam.reward_achievement .steam_id + end if + end with + + with achievement_progress(index) + .rewarded = true + .rewarded_date = NOW + + ' if permanent, persist in global store + if is_permanent() then + ach_debug("adding achievement to persist.reld") + + dim persist as NodePtr = get_persist_reld() + dim path as string = "/achievements/achievement[" & .id & "]" + if NodeByPath(persist, path) = 0 then + ach_debug("achievement not in persist.reld... adding now!") + dim achievement as NodePtr = NodeByPath(persist, path, true) + SetChildNodeDate(achievement, "rewarded_date", NOW()) + write_persist_reld + else + ach_debug("achievement already in persist.reld, skipping") + end if + else + ach_debug("achievmeent is per-save, so not adding to persist") + end if + end with + end sub + + #ifdef DEBUG_ACHIEVEMENTS + private sub ach_debug(msg as string) + debug "achievements: " & msg + end sub + #endif + +end namespace diff --git a/allmodex.bas b/allmodex.bas index 54be2e11e..0d183aba3 100644 --- a/allmodex.bas +++ b/allmodex.bas @@ -23,6 +23,7 @@ #include "uiconst.bi" #include "slices.bi" #include "loading.bi" +#include "steam.bi" using Reload @@ -1670,6 +1671,7 @@ function dowait () as bool dim starttime as double = timer do while timer <= waittime - 0.0005 io_waitprocessing() + Steam.run_frame sleep bound((waittime - timer) * 1000, 1, 5) loop ' dowait might be called after waittime has already passed, ignore that diff --git a/custom.bas b/custom.bas index a1c824aa1..f6bf96bbe 100644 --- a/custom.bas +++ b/custom.bas @@ -954,10 +954,7 @@ FUNCTION newRPGfile (templatefile as string, newrpg as string) as bool IF vernode THEN FreeNode vernode '--Set creation time, wipe edit_time - SetChildNode(root_node, "edit_time", 0.) - DIM created_node as NodePtr - created_node = SetChildNode(root_node, "created", NOW) - SetChildNode(created_node, "str", format_date(NOW)) + SetChildNodeDate(root_node, "edit_time", 0.) close_general_reld @@ -967,12 +964,6 @@ FUNCTION newRPGfile (templatefile as string, newrpg as string) as bool RETURN write_rpg_or_rpgdir(workingdir, newrpg) END FUNCTION -' Argument is a timeserial -FUNCTION format_date(timeser as double) as string - IF timeser = 0 THEN RETURN "0" - RETURN FORMAT(timeser, "yyyy mmm dd hh:mm:ss") -END FUNCTION - 'Returns the last mtime of any file in a directory (excluding *.tmp) FUNCTION directory_last_mtime(directory as string) as double DIM lasttime as double = 0 diff --git a/custom.bi b/custom.bi index 00692de2a..f7a2621a1 100644 --- a/custom.bi +++ b/custom.bi @@ -80,7 +80,6 @@ DECLARE FUNCTION export_translations (fname as string) as bool DECLARE FUNCTION compile_andor_import_scripts (f as string, quickimport as bool = NO) as bool DECLARE SUB reimport_previous_scripts () DECLARE SUB write_session_info () -DECLARE FUNCTION format_date(timeser as double) as string DECLARE SUB common_elementals_editor(elementals() as single, helpfile as string, byval showsign as integer = 0) DECLARE SUB tile_anim_draw_range(tastuf() as integer, byval taset as integer, byval page as integer) diff --git a/customsubs.rbas b/customsubs.rbas index 14ee4fe1a..4a7354892 100644 --- a/customsubs.rbas +++ b/customsubs.rbas @@ -2725,10 +2725,8 @@ SUB save_edit_time () SetContent(timenode, GetFloat(timenode) + active_seconds - last_active_seconds) debuginfo "Saving - been editing for " & format_duration(active_seconds - last_active_seconds, 0) - DIM saved_node as NodePtr - saved_node = SetChildNode(gen_root, "last_saved", NOW) - SetChildNode(saved_node, "str", format_date(NOW)) - + SetChildNodeDate(gen_root, "last_saved", NOW) + last_active_seconds = active_seconds write_general_reld() END SUB diff --git a/game.bas b/game.bas index 53491a0fc..32ecb4d6c 100644 --- a/game.bas +++ b/game.bas @@ -28,7 +28,9 @@ #include "gfx.bi" #include "pathfinding.bi" #include "bcommon.bi" - +#include "steam.bi" +#include "achievements.bi" +#include "achievements_runtime.bi" 'local subs and functions DECLARE SUB checkdoors () @@ -210,7 +212,7 @@ set_app_dir log_dir = CURDIR & SLASH #ENDIF -'set_OPEN_hook @hook_all_files 'For debugging +set_OPEN_hook @hook_all_files 'For debugging 'Once log_dir is set, can create debug log. external_log "starting debug log..." @@ -264,6 +266,12 @@ IF running_under_Custom THEN debuginfo "Spawned from Custom" '============================== Initialise backends =========================== +IF Steam.Initialize() THEN + debuginfo "Steam initialized" +ELSE + debuginfo "Steam not initialized" +END IF + 'DEBUG debug "set mode-X" set_resolution read_config_int("gfx.resolution_w", 320), read_config_int("gfx.resolution_h", 200) IF overrode_default_zoom = NO THEN @@ -627,6 +635,11 @@ SetupGameSlices 'This is called BEFORE the loop, because when the game is quit or a save is loaded, this will be called again there reset_game_state +debug "test" + +'Load achievement data +Achievements.definitions_load workingdir & SLASH & "achievements.reld" + '===================== Stuff reinitialised each new/load-game ================== DO' This loop encloses the playable game for a specific RPG file @@ -714,6 +727,8 @@ ELSEIF load_slot >= 0 THEN ELSE 'New game refresh_purchases() + 'clear existing achievement progress (with no save file to refresh from) + Achievements.runtime_load(null) 'This fadeout means that resetgame fades out the screen although gameover doesn't fadeout uilook(uiFadeoutNewGame) 'Clear the screen so that there's no garbage shown behind the prompt to rename the starting hero @@ -962,7 +977,6 @@ reset_game_final_cleanup 'This may call exitprogram LOOP ' This is the end of the DO that encloses the entire program. - '========================================================================================== '========================================================================================== @@ -3028,6 +3042,7 @@ END FUNCTION SUB tag_updates (npc_visibility as bool=YES) IF npc_visibility THEN visnpc update_menu_items + Achievements.evaluate_tags END SUB ' Updates which menu items are enabled (for any reason, not just tags) diff --git a/moresubs.bi b/moresubs.bi index d20dc48e4..ec50c4389 100644 --- a/moresubs.bi +++ b/moresubs.bi @@ -6,6 +6,7 @@ #define MORESUBS_BI #include "gfx.bi" +#include "game_udts.bi" DECLARE SUB addhero (who as integer, slot as integer, forcelevel as integer = -1, allow_rename as bool = YES, loading as bool = NO) DECLARE SUB deletehero (slot as integer, resetting_game as bool = NO) diff --git a/moresubs.rbas b/moresubs.rbas index 1ae6b64ec..29745945d 100644 --- a/moresubs.rbas +++ b/moresubs.rbas @@ -25,6 +25,7 @@ #include "bmodsubs.bi" #include "purchase.bi" #include "scripting.bi" +#include "achievements_runtime.bi" '--Local subs and functions DECLARE SUB teleporttool_load_map (map as integer, maptiles2() as TileMap, pass2 as TileMap, tilesets2() as TilesetData ptr) @@ -1529,6 +1530,8 @@ SUB reset_game_state () remap_virtual_gamepad("virtual_gamepad") + Achievements.runtime_reset + END SUB SUB remap_virtual_gamepad(nodename as string) diff --git a/reload.bas b/reload.bas index 644686874..2cfcc034c 100644 --- a/reload.bas +++ b/reload.bas @@ -1487,6 +1487,18 @@ Function SetChildNode(byval parent as NodePtr, n as zstring ptr, val as string) return ret end Function +'Sets teh child node of name n to a double value. If n doesn't exist, it adds it. +'Also, adds a child of n called "str" with a formatted date string +Function SetChildNodeDate(byval parent as NodePtr, n as zstring ptr, val as double) as NodePtr + dim node as NodePtr = SetChildNode(parent, n, val) + + if node then + SetChildNode(node, "str", format_date(val)) + end if + + return node +end Function + 'Toggle a node to a zero/nonzero value (sets it to 0 or 1). Creates the node if it does not exist Sub ToggleBoolChildNode(byval parent as NodePtr, n as zstring ptr) if parent = 0 then exit sub diff --git a/reload.bi b/reload.bi index 4b8e244e6..09a001b0d 100644 --- a/reload.bi +++ b/reload.bi @@ -170,6 +170,7 @@ Declare Function SetChildNode Overload (byval parent as NodePtr, n as zstring pt Declare Function SetChildNode(byval parent as NodePtr, n as zstring ptr, byval val as longint) as NodePtr Declare Function SetChildNode(byval parent as NodePtr, n as zstring ptr, byval val as double) as NodePtr Declare Function SetChildNode(byval parent as NodePtr, n as zstring ptr, val as string) as NodePtr +Declare Function SetChildNodeDate(byval parent as NodePtr, n as zstring ptr, val as double) as NodePtr Declare Sub ToggleBoolChildNode(byval parent as NodePtr, n as zstring ptr) Declare Sub ToggleChildNode(byval parent as NodePtr, n as zstring ptr) Declare Sub FreeChildNode(byval parent as NodePtr, n as zstring ptr) diff --git a/savegame.rbas b/savegame.rbas index d8272626b..ae4abb2b7 100644 --- a/savegame.rbas +++ b/savegame.rbas @@ -17,6 +17,7 @@ #include "moresubs.bi" #include "menustuf.bi" #include "scriptcommands.bi" +#include "achievements_runtime.bi" USING Reload USING Reload.Ext @@ -292,6 +293,8 @@ SUB gamestate_from_reload(byval node as Reload.NodePtr) fadeout 0, 0, 0 END IF + Achievements.runtime_reset ' notify the acheivement subsystem that tags are going to go crazy for a bit + READNODE node, default node."ver".ignore node."game_client".ignore @@ -306,6 +309,7 @@ SUB gamestate_from_reload(byval node as Reload.NodePtr) gamestate_vehicle_from_reload node."vehicle".warn.ptr 'Added later gamestate_slices_from_reload node."slices".ptr + Achievements.runtime_load node."achievements".default(null).ptr END READNODE END SUB @@ -861,6 +865,7 @@ SUB gamestate_to_reload(byval node as Reload.NodePtr) gamestate_inventory_to_reload node gamestate_shops_to_reload node gamestate_vehicle_to_reload node + Achievements.runtime_save node END SUB SUB gamestate_state_to_reload(byval parent as Reload.NodePtr) diff --git a/steam.bas b/steam.bas new file mode 100644 index 000000000..d0cbda895 --- /dev/null +++ b/steam.bas @@ -0,0 +1,244 @@ +'OHRRPGCE GAME - code for interfacing with Steamworks +'(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and Hamster Republic Productions +'Please read LICENSE.txt for GPL License details and disclaimer of liability +' +#include "common_base.bi" +#include "steam.bi" +#include "steam_internal.bi" + +' #define DEBUG_STEAM + +namespace Steam + +#ifdef DEBUG_STEAM +declare sub steam_debug(msg as string) +#else +#define steam_debug(x) +#endif + +' event handlers + +declare sub OnUserStatsReceived(msg as UserStatsReceived_t ptr) + +' static variables + +dim shared steamworks_handle as any ptr = null + +' reminder: these are all function pointers, not function declarations +' basic init/deinit +dim shared SteamAPI_Init as function() As boolean +dim shared SteamAPI_Shutdown as sub() +dim shared SteamAPI_RestartAppIfNecessary as function( byval unOwnAppID as uinteger ) as boolean + +' callback infrastructure +dim shared SteamAPI_GetHSteamPipe as function() as HSteamPipe +dim shared SteamAPI_ManualDispatch_Init as sub() +dim shared SteamAPI_ManualDispatch_RunFrame as sub( byval hSteamPipe as HSteamPipe ) +dim shared SteamAPI_ManualDispatch_GetNextCallback as function ( hSteamPipe as HSteamPipe, pCallbackMsg as CallbackMsg_t ptr) as boolean +dim shared SteamAPI_ManualDispatch_FreeLastCallback as sub ( hSteamPipe as HSteamPipe) +dim shared SteamAPI_ManualDispatch_GetAPICallResult as function ( hSteamPipe as HSteamPipe, hSteamAPICall as SteamAPICall_t, pCallback as any ptr, cubCallback as integer, iCallbackExpected as integer, pbFAiled as boolean ptr) as boolean + +' achievements +dim shared SteamAPI_SteamUserStats_v012 as function () as ISteamUserStats ptr +dim shared SteamAPI_ISteamUserStats_RequestCurrentStats as function(byval self as ISteamUserStats ptr) as boolean +dim shared SteamAPI_ISteamUserStats_SetAchievement as function(byval self as ISteamUserStats ptr, byval name as const zstring ptr) as boolean +dim shared SteamAPI_ISteamUserStats_ClearAchievement as function(byval self as ISteamUserStats ptr, byval name as const zstring ptr) as boolean +dim shared SteamAPI_ISteamUserStats_StoreStats as function(byval self as ISteamUserStats ptr) as boolean +dim shared SteamAPI_ISteamUserStats_IndicateAchievementProgress as function(byval self as ISteamUserStats ptr, byval name as const zstring ptr, progress as uinteger, max_progress as uinteger) as boolean + + +dim shared steam_user_stats as ISteamUserStats ptr + +#macro MUSTLOAD(hfile, procedure) + procedure = dylibsymbol(hfile, #procedure) + if procedure = NULL then + steam_debug("Was not able to find " & #procedure) + ' do this instead of uninitialize_steam(), since it assumes we succeeded in initializing + dylibfree(hFile) + hFile = null + return NO + end if +#endmacro + +function initialize() as boolean + + #ifdef __FB_WIN32__ + #ifdef __FB_64BIT__ + 'Future, although not yet supported + #define STEAM_LIB "steam_api64" + #define STEAM_FULL_FNAME "steam_api64.dll" + #else + #define STEAM_LIB "steam_api" + #define STEAM_FULL_FNAME "steam_api.dll" + #endif + #elseif defined(__FB_DARWIN__) + #define STEAM_LIB "steam_api" + #define STEAM_FULL_FNAME "libsteam_api.dylib" + #else + 'Either Linux or maybe a BSD (on which Steam isn't officially support but can be run) + #define STEAM_LIB "steam_api" + #define STEAM_FULL_FNAME "libsteam_api.so" + #endif + + steamworks_handle = dylibload(STEAM_LIB) + if steamworks_handle = null then + steam_debug("Was not able to open " STEAM_FULL_FNAME) + return false + end if + + MUSTLOAD(steamworks_handle, SteamAPI_Init) + MUSTLOAD(steamworks_handle, SteamAPI_Shutdown) + MUSTLOAD(steamworks_handle, SteamAPI_RestartAppIfNecessary) + MUSTLOAD(steamworks_handle, SteamAPI_GetHSteamPipe) + MUSTLOAD(steamworks_handle, SteamAPI_ManualDispatch_Init) + MUSTLOAD(steamworks_handle, SteamAPI_ManualDispatch_RunFrame) + MUSTLOAD(steamworks_handle, SteamAPI_ManualDispatch_GetNextCallback) + MUSTLOAD(steamworks_handle, SteamAPI_ManualDispatch_FreeLastCallback) + MUSTLOAD(steamworks_handle, SteamAPI_ManualDispatch_GetAPICallResult) + MUSTLOAD(steamworks_handle, SteamAPI_SteamUserStats_v012) + MUSTLOAD(steamworks_handle, SteamAPI_ISteamUserStats_RequestCurrentStats) + MUSTLOAD(steamworks_handle, SteamAPI_ISteamUserStats_SetAchievement) + MUSTLOAD(steamworks_handle, SteamAPI_ISteamUserStats_ClearAchievement) + MUSTLOAD(steamworks_handle, SteamAPI_ISteamUserStats_StoreStats) + MUSTLOAD(steamworks_handle, SteamAPI_ISteamUserStats_IndicateAchievementProgress) + + if SteamAPI_Init() = false then + steam_debug("unable to initialize steamworks") + uninitialize() + return false + end if + + ' todo: is this necessary? + ' if SteamAPI_RestartAppIfNecessary( ourAppId ) <> false then + ' steam_debug("Steam seems to want to restart the application for some reason") + ' end if + + SteamAPI_ManualDispatch_Init() + + ' all stuff to do with stats and achievements go through SteamUserStats interface: + steam_user_stats = SteamAPI_SteamUserStats_v012() + + if steam_user_stats = null then + steam_debug("Unable to obtain user stats object") + else + ' we need to instruct steam to fetch the user stats, so we can reward achievements later + if SteamAPI_ISteamUserStats_RequestCurrentStats(steam_user_stats) = false then + steam_debug("Unable to request current stats") + end if + end if + + return true + +end function + +sub uninitialize() + if steamworks_handle <> null then + dylibfree(steamworks_handle) + steamworks_handle = null + end if +end sub + +function available() as boolean + return steamworks_handle <> null +end function + +sub reward_achievement(id as const string) + if available() = false then return + + if SteamAPI_ISteamUserStats_SetAchievement(steam_user_stats, id) = false then + steam_debug("unable to reward achievement: " & id) + else + if SteamAPI_ISteamUserStats_StoreStats(steam_user_stats) = false then + steam_debug("unable to persist stats") + end if + end if +end sub + +sub clear_achievement(id as string) + if available() = false then return + + if SteamAPI_ISteamUserStats_ClearAchievement(steam_user_stats, id) = false then + steam_debug("unable to clear an achievement: " & id) + end if +end sub + +sub notify_achievement_progress(id as const string, progress as integer, max_progress as integer) + if available() = false then return + + if SteamAPI_ISteamUserStats_IndicateAchievementProgress(steam_user_stats, id, progress, max_progress) = false then + steam_debug("unable to indicate achievement progress: " & id) + end if +end sub + +#macro CALLBACK_HANDLER(typ, handler) + case typ.k_iCallback + steam_debug(#typ ", message length: " & callback.m_cubParam) + dim typ##Msg as typ ptr = cast(typ ptr, callback.m_pubParam) + handler(typ##Msg) +#endmacro + +#macro IGNORE(id) + case id + ' steam_debug("Ignored Steam id: " & id) +#endmacro + +dim shared achieve_timer as integer = -1 + +sub run_frame() + if available() = false then return + + ' steam_debug("run_steam_frame") + + dim hSteamPipe as HSteamPipe = SteamAPI_GetHSteamPipe() + SteamAPI_ManualDispatch_RunFrame(hSteamPipe) + dim callback as CallbackMsg_t + while SteamAPI_ManualDispatch_GetNextCallback(hSteamPipe, @callback) + ' Check for dispatching API call results + if callback.m_iCallback = 703 then + dim pCallCompleted as SteamAPICallCompleted_t ptr = cast(SteamAPICallCompleted_t ptr, @callback) + dim pTmpCallResult as any ptr = allocate(pCallCompleted->m_cubParam) + dim bFailed as boolean + if SteamAPI_ManualDispatch_GetAPICallResult ( hSteamPipe, pCallCompleted->m_hAsyncCall, pTmpCallResult, pCallCompleted->m_cubParam, pCallCompleted->m_iCallback, @bFailed ) then + ' Dispatch the call result to the registered handler(s) for the + ' call identified by pCallCompleted->m_hAsyncCall + steam_debug("Call Completed handler") + end if + deallocate(pTmpCallResult) + else + ' Look at callback.m_iCallback to see what kind of callback it is, + ' and dispatch to appropriate handler(s) + select case callback.m_iCallback + CALLBACK_HANDLER(UserStatsReceived_t, OnUserStatsReceived) + ' these are messages that we either don't know the identity of, or we don't care + IGNORE(715) + IGNORE(304) + IGNORE(711) + IGNORE(903) + IGNORE(501) + IGNORE(502) ' favorites list changed + IGNORE(1006) + IGNORE(1102) ' user stats stored + case else + steam_debug("Some other handler: " & callback.m_iCallback) + end select + end if + SteamAPI_ManualDispatch_FreeLastCallback(hSteamPipe) + wend +end sub + +private sub OnUserStatsReceived(msg as UserStatsReceived_t ptr) + steam_debug("On User Stats Received") + + ' unsure if we actually need to do anything in response to this. + ' TODO: buffer any achievement activity that happens before this call? + + ' achieve_timer = 1000 +end sub + +#ifdef DEBUG_STEAM +sub steam_debug(msg as string) + debug "steam: " & msg +end sub +#endif + +end namespace \ No newline at end of file diff --git a/steam.bi b/steam.bi new file mode 100644 index 000000000..f339c18ec --- /dev/null +++ b/steam.bi @@ -0,0 +1,20 @@ +'OHRRPGCE GAME - code for interfacing with Steamworks +'(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and Hamster Republic Productions +'Please read LICENSE.txt for GPL License details and disclaimer of liability +' +#IFNDEF STEAM_BI +#DEFINE STEAM_BI + +namespace Steam + +declare function initialize() as boolean +declare sub uninitialize() +declare function available() as boolean +declare sub run_frame() +declare sub reward_achievement(id as const string) +declare sub clear_achievement(id as string) +declare sub notify_achievement_progress(id as const string, progress as integer, max_progress as integer) + +end namespace + +#ENDIF diff --git a/steam_internal.bi b/steam_internal.bi new file mode 100644 index 000000000..ec51d393e --- /dev/null +++ b/steam_internal.bi @@ -0,0 +1,44 @@ +'OHRRPGCE GAME - typedefs for interfacing with Steamworks +'(C) Copyright 1997-2020 James Paige, Ralph Versteegen, and Hamster Republic Productions +'Please read LICENSE.txt for GPL License details and disclaimer of liability +' +#ifndef STEAM_INTERNAL_BI +#define STEAM_INTERNAL_BI + +#ifdef __FB_WIN32__ +#define STEAM_PACK 8 +#else +#define STEAM_PACK 4 +#endif + +type ISteamUserStats as any +type HSteamPipe as integer +type HSteamUser as integer +type SteamAPICall_t as uint64 +type CSteamID as uint64 ' there is more nuance to this, but this will do for now +type EResult as uint32 + +type CallbackMsg_t field = STEAM_PACK + m_hSteamUser as HSteamUser + m_iCallback as integer + m_pubParam as ubyte ptr + m_cubParam as integer +end type + +type SteamAPICallCompleted_t field = STEAM_PACK + const k_iCallback as integer = 703 + m_hAsyncCall as SteamAPICall_t + m_iCallback as integer + m_cubParam as uinteger +end type + +type UserStatsReceived_t field = STEAM_PACK + const k_iCallback as integer = 1101 + m_nGameId as uint64 + m_eResult as EResult + m_steamIDUser as CSteamID +end type + +declare sub OnUserStatsReceived(msg as UserStatsReceived_t ptr) + +#endif \ No newline at end of file diff --git a/testgame/achievements.hss b/testgame/achievements.hss new file mode 100644 index 000000000..a95f90657 --- /dev/null +++ b/testgame/achievements.hss @@ -0,0 +1,21 @@ +global variable (1, slime count) + +plotscript, deal with slimes, begin + if (check tag(5)) then, begin + set tag(5, off) + slime count += 1 + play sound (1, false, false) + + if(slime count >= 5) then, begin + set tag(3, on) + end + end +end + +plotscript, star, ignored, npc, begin + show text box(8) + settag(6, on) + wait(1) + settag(6, off) + destroy npc(npc) +end \ No newline at end of file diff --git a/testgame/achievements.rpgdir/achievements.reld b/testgame/achievements.rpgdir/achievements.reld new file mode 100644 index 0000000000000000000000000000000000000000..d02eaeed7f5fe7131e918fcc89a0624fc5db73ca GIT binary patch literal 414 zcmZ{fO-}+b5QeAFEi8)>5==}y*nj|7h~-XRLn8pbBh*SM5!jdGlQK%MF} z!9Nqdf`H;W&@6yYdO~c7b~qTRxBHp8?)B7NIE|iQOcx%j@MEG7!7#IOx|e;_#s78{ zIBd`mtslFGSyb9VR=QQidFggG!o8ejUzhSdE4mh?HZn|-tK4Y|KDE?@x5x_P{%$J{ ze}#Z~C7X=FDdi2(IXw3~4_~8+8isQf4hAn%Xg2j?9pe^LG%l|h+&L95*@eGBn`%SF r;8z1TIz{gG8xp + + 4 + + + 1 + Finished the Job + flag + + 4 + + ACH_WIN_100_GAMES + + + 2 + For My Beau + flag + + 2 + + ACH_WIN_ONE_GAME + + + 3 + Hey Now, You're an All-star + count + + 6 + + 5 + 1 + ACH_TRAVEL_FAR_ACCUM + + \ No newline at end of file diff --git a/testgame/achievements.rpgdir/archinym.lmp b/testgame/achievements.rpgdir/archinym.lmp new file mode 100644 index 000000000..c392f9758 --- /dev/null +++ b/testgame/achievements.rpgdir/archinym.lmp @@ -0,0 +1,2 @@ +ohrrpgce +OHRRPGCE wip 20211026 diff --git a/testgame/achievements.rpgdir/attack.bin b/testgame/achievements.rpgdir/attack.bin new file mode 100644 index 0000000000000000000000000000000000000000..c44e5d13769f1b9a870e7b05fb680a77c61efa33 GIT binary patch literal 616 QcmZQz7zLvtK>H8?00L+L0RR91 literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/attacks.rgfx b/testgame/achievements.rpgdir/attacks.rgfx new file mode 100644 index 0000000000000000000000000000000000000000..57e4fc20d791d78550ce92b8b7d2e4ebc5039704 GIT binary patch literal 7744 zcmeI%%MF4+5C-5GwOxI&OBYFh4M3%*XiyJjyEe- literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/backdrops.rgfx b/testgame/achievements.rpgdir/backdrops.rgfx new file mode 100644 index 0000000000000000000000000000000000000000..172082caa46ad60bc8fab378ad078f5ea248ec06 GIT binary patch literal 64184 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 literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/boxborders.rgfx b/testgame/achievements.rpgdir/boxborders.rgfx new file mode 100644 index 0000000000000000000000000000000000000000..2cd9427862e2755d5b057da7e79ec11dc1bfd0d6 GIT binary patch literal 4652 zcmeI0Us8iG5XKVjWvOpl~*K6wM2LS?449bnW)@8S`B^9EkQ)3_n|TDI+*#NmO= zz?V&66MkfO0uOihw=xkz{M-ofO@|agNM<0V_#$&6-lIk<-WKPwHjz#=#dVLg)Vx4P+(%NLEt~fhQ-373fvK|h$Zfo|WRjT^385ik| z3wS`m0}38cv_o+pv_r#Bm2nA1E}+b~OhzsjxrF|?xN#95$Op&xu?fQUQ+~0wjUUQC zxS{paXF@R!JO0>ui8zVVWKiT*5?*e*HC~X*0;zed(_O_c;_Z$0$F8}K%8 z0`UfY0Nw*;_H*KR*LG?OYQ0juU*n(uGrOMHXzrPPZd2?Li0o5x zCW7KJ!g(xK8!_!LF$NZhGU!4h_cF>LvRQBgoGfXPa~W|B&fv{OA!RLuoj7JFQAj7T zjRVvu3Nt1l6lwye@DTyRHZKIi4b-@)4a8yq*AYs%E^{Jn5eSOU79ooGBL>Kk%w3)) zCZ%hTl=@KjJL|7U@&5ki){NEwTbB|=66E)3qXJAJ1+)wC2p1XT_ zg4KA^M4*X46M-fIO$3?4%YgP<+VV-oyvRmj4)P{X+aw zJ}8QsJ}BM*GxDI=hHkHOGPpwFY+U_U5g>1ybx4T3T7i-0)e8LId93F6)y`wJE-&o3 zx_NbM;=jj$uTN@%i!WmE_Navz;hiB$1)$X(f~#-?TklRI4kea#n4gt3=5dpr$|`GO zTcJfB!sc1nTbh@{s=|Yt-CR6h8sdzN%~3s$5Ye$ntH@K}yr>BjKb8%k#1+sg_{)J# zSF7uD;ld^2SB6(1fKHs$S9(W7Iw#ARZH8X0ywLE&k;YDR*O7$UUmR4jOYB|Fak3v z+mW%X0fhZbH_RN->Mmqc3lMR z!fB%5319&)Pa&7Z1xh04$-}Zd7BUNMGIt#JkHD#-(_9j+TrcbAHM#f(!MP2jYufF%EJ}>gg4) zyyzPlsYDZ0WG+Ja=n^!kbPk9t<-xMG)FY~8A(#0)^-mW0W1Q`HvX|%|<2(V+1LpUj zYWq*0^_Yj|9r*2z7&Q+_VlW`;>Ksc-J{hxl2`5hgBMBx05VyC zzcK&{6ix(HNUVvFP(Uq;nAB!=f`FZoFX(v)A{A0v>P6+H?ZRr5rl@iN@|JyPP$n(ex$k&kTw^MqWW)MZSf68~F}0LB5N8 z4|xswKJo|150D=suOpX{A0clbKSr(~SCKc7bz}qCM6Mw}L9Qb=kekRYIhnKNaVG7BqS6=al%^UsDv2pM&{HT$se`lejpEH$=>dyrmTU@Kf`*XTLs& zhk`d#i)U|IZ{Gg-Tv-xa))q|NG{_+v5u-tLW4pGsU2jCGE2%%`{ytp!1*IN2oS02w z>f2^(yIzZKvKoJ)ZfWA%D0aOKV2E%B)Oiu^fhr4LQ&ywG*F<4*`u=~X<+TJrc6}Ez zQV-${MdecKaVW>(Q5-hn@Hh??fRPHILE?9DL0Zckeb#Qj{B&AZOk{ zbYU8XZZ@ihS+6%q_x0Ec-JZGOdI5F#c;j_vvp)Un1eEUX9Dlw?jH=$M8f0Bw zU(uTty;UY%$07AE32fgvvE+s`9dTS-c^jG$+j#Qg z(q#R@Gqgwh)aQ0DOizXdpZV71bgIvWA%|Jg`#-&~Ywb>-75_F2c9CY2nK$)--rq=D_0Mqa>&;^|MU i#V=qHOGRZ&;&Rp;47cZo$$?YC(PUl zGk3E3mz{Ls5KCVHZyU`jcs`oTAaHQt3chYM_dV~q)-sybZ}fqAvk#4ZWb78&x}M4wB&fE3}6QfxHV z$2&eF#S=H3bXtl>$mg`j6L&JRXSK)ctUYGd9y3oYGfyltPb@P}Ec3;+M{S_?nlkw> zgwy;xZM?@eP=y?vQZ<+zw@ zyG){PSs-wA587b@+~s zB69a+I#+|^g-jj{n%rTg@_FUZuQN^Z*lTilRjJanaucnxs$WBxCY7!n`t7Pz;SMA% zcdI&WSd=;`@~W#2$}q#yylA4LX*GT4*X09f|ywR$Hd1WWIRre#)FChj#$pCQ zjsX&)CEx@PN%BQql8eazwETghoFws*RwF_J<3!Wo!|0&GOg%=RfNn86s;i^JF32A1 zTlr=D2ms0qTYN%8#Q3GQSzUIE_09ctVS|h8T-3Yd-by3WD@7j)`}h?iiLyD`m{I>4vv!mUrRjFeEVgBuHmNItE0 z^a*r#whb;0or^m+(0BND2O_!5ENiam5o_7N+E)F4VA1av_0z}lZg4SkE?RwZe{~&j zF3LSAx}de#e@Fky@94wD{32edQ+xIaM(xyeU&S48Fgh6bZGv$FjHf=sSTO=Tz19H- zql0m;!T980)UI3iRonpwql0m;!T9Q6tQY~FUh9B^(ZRTHV3hgZ9{xq|gRA|yOA?E)N+JmY05D()CjbBd literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/menuitem.bin b/testgame/achievements.rpgdir/menuitem.bin new file mode 100644 index 0000000000000000000000000000000000000000..fccf9a097486f5cf116bc7d109d0928dd376eb81 GIT binary patch literal 816 zcmbu5ISzm@5Cd~S5}@Ju|6ehUWeGR0yZpgoVNMy NSmC$^9UC00b==j)0LlOW literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/menus.bin b/testgame/achievements.rpgdir/menus.bin new file mode 100644 index 0000000000000000000000000000000000000000..3766ae0f948a661632f29dfdadd5c933152965ba GIT binary patch literal 56 ScmZQzzzrCYr1+57ASM6-Gyo6) literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.d00 b/testgame/achievements.rpgdir/ohrrpgce.d00 new file mode 100644 index 0000000000000000000000000000000000000000..ed2b351ea0baf9962175e497a19308f9fb97e639 GIT binary patch literal 2007 ocmex6b0!1B1$G98|D#|Kg#g1S7!85Z5Eu=C(GVC70Wv}W02SzuhyVZp literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.dox b/testgame/achievements.rpgdir/ohrrpgce.dox new file mode 100644 index 0000000000000000000000000000000000000000..c5951433e723d06e438339379b1c74b2634afb91 GIT binary patch literal 12000 zcmeIuF#!Mo0K%a4Pi+hzh(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM X7%*VKfB^#r3>YwAz<>b*2L1yBF5mzG literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.dt0 b/testgame/achievements.rpgdir/ohrrpgce.dt0 new file mode 100644 index 0000000000000000000000000000000000000000..c5421310a97f80695f66d81e977f34f2a51cebfe GIT binary patch literal 1716 zcmZQ(aAhcCNM*nW{{LrS0AU7(6ovhk#300=479Yteq>|Xh5M+RNKnP70#FDv z*pF-sO9fmEz6^;B6$}bQq<1z3Bd|6Tm}U~`|NohQI#S@eKrDVBNg5E{r6g~CVSP*W2gnBCEc#8;0VJ$faIr$o`!i(%{Fpeo5pNlMBRC345P z1W~H^@ZrPlHc@Qb#-W&Mn)v^BZ!mj&oDF)VaF%7m5=mGY(30c~KmG>-OH0kmKJxKH zC&P~@F>)t)XABXEha@o$KTFfom!q>Jws@P@<;;rCPx?wm=GZA^B)kU}AOLx5zsl7v zF_%u&m8+?6z~>bx!*pF|8KN9x8m{G**UHAs1jTe?dS>$+DT#QDu_*OV777Jtl+?zQ zVboG22+M1|!4^t0!!THyd^3jeX*L22w{Bs>`k{%EoOTH&_~LqfC5`xagz|h4j>>VP zGTjj%%kF-++qQ`PYmd97>v71s>zS*Eql~q-wxVeG^YYqx$dbeuH;bEq)GFM`3a(z> zSOIE$5o-j(fd+Fig%{rX%~*STdtVzxvaxf^>^isKJ9=+|KDakMzj>bECu0J>mxUdc zB?|VXs;RypA=v?>oK`kuiGQahXjupcEw9Z_PmXgYAdX$|`U2rp6({h*PKdfMK;*LG z#5_K?tQ9+-S1s^l#iE7k<#K)i{#eB#_fN4+b1{rGHV^<0(YJfgpC4H~9+G6Oa>MmH z->dsuTd#3qETr7cLjnF8z_Q^#;%i%?2ypG&3enL~BB8==SWP63j<(3QC6PCpv}l^^ zQFNck7?)LB(^MPuW%w@#NLH(+2ub$;S{r&5~1i#IWiXXQ10}H1Dzu}c|Ip0sU_*ug-zPLa0-F=!uJLI^t$M! zP*hQsXq3p8Q8T=rU6iS9Zm>K!*GB$@tJ>8CKUK>!`b@b>a{7V(AT3iMlCd+90HtQF zPwX2P;mPaQ*mxcq4K&I?gAI<~MEye12&upC|vxcY=Og zZtv>qJK226gE2u{N6iB81%pgrP*50EgSEA@uK_&4AeYF~)7903`i!ULB=7TS5#m4GKY{{YwMP1fIo5~I4A+}DQeUH*)7{bS z)bRZ5nX`C~^5>&~KKkv;pZ+NO$#$;Hnc!{P=G>J6uu-X0CV)=GtUwkD4cjfpyxk~( zwx{4Y1@IFJw#{X3j8D#n)7ueu98#Rgi98wu{Wme`02`Ql4TZ*>!o&oZokG3$ z_Zl}Ymu_VQ)fvz6`d97TxWm!Wb&eMuO>>amfPDYK`{;X~T;6bh9}6GP)KiqIO_h{Y z4Simk8s%6wj4|N(no$~C<8rAucAr0=nz{h}z`BW1aFqhrzxw&mL?1hqxkY3G%DHR3 z1fG@3wfBfd9n<78k+E4voY5)uO`T(@R?7iAC94js9&CU8%^QpAYps5S+vURt5av#4 z3Ui+GF~F1aDYx(9Ur^?fA?}{pU34N?=X}^ literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.for b/testgame/achievements.rpgdir/ohrrpgce.for new file mode 100644 index 0000000000000000000000000000000000000000..1ecc2df4533923e943d7de3f4ff79674c5449de3 GIT binary patch literal 320 zcmZQ%2xdrPU|?WmP-AES(&PeGs76LK^B5Vl7(&P}6k%Q(gC|fwBZCw}I+%82hzHXt c3{51M3p0<2L65-&tS^tDodo^pN?D)+08oDibpQYW literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.gen b/testgame/achievements.rpgdir/ohrrpgce.gen new file mode 100644 index 0000000000000000000000000000000000000000..e8ff7bf2a19e76d111f96c2d89fe8a094c7f1e8f GIT binary patch literal 1007 zcmex6b0!1B3uXo&U<6_&21XEt0iH7~VmQL^90*@997L04LEkBgl7Vg&fO0l7{QoZsW;Zbi0mYd(I2bs9e1vM4u7RMz z0SmTRMUoZ40gJFR@G*!0Ap=7)7&|bY0MaZB=3rR{1_dC^2!tTO!N3Y6y@3!FAo~&) QQV128A_%!r)<_Hi03kaP`v3p{ literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.hsp b/testgame/achievements.rpgdir/ohrrpgce.hsp new file mode 100644 index 0000000000000000000000000000000000000000..2f16bee9592d870f7aa56f01647affc2b0b649cb GIT binary patch literal 183893 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 literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.itm b/testgame/achievements.rpgdir/ohrrpgce.itm new file mode 100644 index 0000000000000000000000000000000000000000..7b8d919cfebfea97b5ff29f76787b7fe15eb8ce7 GIT binary patch literal 1440 kcmZQz7z&`lo`GQ`z>% literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.l00 b/testgame/achievements.rpgdir/ohrrpgce.l00 new file mode 100644 index 0000000000000000000000000000000000000000..4500c4c5578accda3ceca1d0f234fdbbaf272ed5 GIT binary patch literal 3607 zcmeIyy9$6X3`XH&wFve0{%>pBEbf_vlRzjPGJO05W;F^M2PYRdJ5PK&6-WuJJyj!H mqvO3`E07haGfkD~(lcc#kQazc^_}^hZcso01r$)=F9I)aD*@R6 literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.map b/testgame/achievements.rpgdir/ohrrpgce.map new file mode 100644 index 0000000000000000000000000000000000000000..1b18ab7945a6adeeba86fcc7016bfe9bc43d2070 GIT binary patch literal 1520 zcmZQz00BlYi6H*`XK;Y05^&P AL;wH) literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.mas b/testgame/achievements.rpgdir/ohrrpgce.mas new file mode 100644 index 0000000000000000000000000000000000000000..e19c8ed061d477cf302bc1d86e5caf74ff3cd699 GIT binary patch literal 1543 zcmY+E3v=2q5QH}*E|1uN2^a#wyul=-kU#?FrQew!+nIi+Oxr0Moh>8n?XFJx_4Os` z2c2KZw@~-0sHUnSJ*dH3TP?+UQkUmQ19ddlL_NJIQ(td-)li>2$2#gmGdZnU?X+Ok zmHAEG`gSL46_sgqh0IAnZ-NxlhoGmL>X96VJJAZnnKs~L+A&(`pm*||v?tF+Iqj-5 ztqAj?&r$U6HL`^8u99bfh9-;?c(2f4jECgdfjtGuR2<3j6P%s4;2!wu$Z{lJXbwjr zdY}U66*L&`5BRoF@&xyLkcP~80kaOr7joucwpja4P6yusanC#l?vi*WqoZx8}KGx3BAHBhc-vzQSbDlO(dK7eSL_KSRM z`q=exxNwGg=4YW34ZC*9t(;+T! z%yjgbu_057yi5MsGkOBsYU<2$%$_{+tpK(;WEEPXrmR*IdJIkl)_r0M=PHb6U}mi9 zpleM9ufa~?Zkl#DGVuzQ-l0$Yv;V(O@%Jqe!9IlD1J2aJuJe|{Fkz)RWc%EM*Nm)N zGz>uAfo(iy$DmgN5=FB?<170{QkW0_DHo@ zl^>Zq2Yc#k%KWnPYR`ynijgD_HPO^7nli-X}$TX|8+OP4@~6_cK`qY literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.p00 b/testgame/achievements.rpgdir/ohrrpgce.p00 new file mode 100644 index 0000000000000000000000000000000000000000..2fbdf6930ee3c4b5d687ce5cb5dbc9682bd60b21 GIT binary patch literal 371 zcmZQz00RjIeg=MA0IY%^q7XsyLqtdwM;HKQ!E8mBfSQjk40I;CkiI@h1VdaO2&f^> SfWv&KGcfFhi^F_|MgssuJ_q#x literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.pal b/testgame/achievements.rpgdir/ohrrpgce.pal new file mode 100644 index 0000000000000000000000000000000000000000..26c91694e05ef5e32a5286c0b490105b2f4f88cf GIT binary patch literal 176 zcmazMo0N06GgqybhJ2cYZ%&QWJa zjaSbD7Q%p`2w^Jq7${)3aV`}M$(Xedola--HovlNdS^S%P1EyjX?2vkpyU)`Nr?~L zRsIjz#s?tUzRIyl8s4dwri~(QS4ZiA9EuexX>Z}?vdHi~0?(|(fG?r!tG%bAoirnRBb<*@C z-}mY$^(JlC9M;u8>pEQ4H2UyDGzDRuC!B8WFMdL)ZzlhnMLvk!-RdZD8bLq_H*LXb zAmTdYxq;tnvpPyoJPo+fjcc6iPa~mW79)>VkJoO7SV-+06PoLvuvWQAk6~)7mSuVN qCzOO)2JCZk?Gf ztPz5cC?>v%65}k%(wp$hNp|Pl z@1A?^`Oe&P&uLdOF9=ue70A<-A)@{G64sO_s&z*cld5Gp3ZrP!L8VcwR1q8aMl8cc ziw%SnHt?5b8VC$h*%pUdVx%QTTVkvw##`e0mN-mBaZEMreFa~t;z)uoty*-JJdz9z zt7eNitA=dfq#KE15<%N@LEMCu8D>Bb6Gq)3FQ!q|5v2^QsuiXomQ_u&b$yxq-rK{W z+sWTe{We7YhveN2WT;55ATebjC#vFTE-TwQ!NhneSIiYk#Pw1kQA{$}Uq-Njbt(r3 z3UVelArFv94#?Y5B_aqyU{p0L(4a2+N1(1_#$QUKX=MB3+m0M}g{~~7OXwp~1H#sS zzga&JoWusu(QfOOnBU&v+y}z2FArzp2cLgr`Qa+HTS3~IyZ3(_Tr-l$j>ze|o`%7! zTn3vH8Ts@GGxqcGz0b^6q zT#}z?stxVXA6FD9XDG-hFI)G?HnD_YebexX3;j=uML3O;-)~;I>Z(Y6n7{bo+T@GA z3~ZdA`0(@i(Lw(N)|z$n(xJz0^=)d-bUzHcKUK&wvf0_Ma_Z8CFQw~?Qt8~gzbDDD zH_*xLv+pn3C(653>$&LV>5pnX#NTYL1d4JxJ&PAlymBFNq(_pjut?@Z#UjlJJ} zw$jS)Uv)9pT{5780EzP4iaRdnju~eP;$Lq_P*(t)K<7^!53^+;i>(@o)Y0%ie{WoV ztxqgsO_|#|cVv7=%ff2o&$)xAcZAM%T{_Uc1*^JA?XFoQ|Frx)_0*X=L$hJ~5P-xa znNYrdq3c-RmLJ~kaZpO8ug^dJ)UWeju6naO-!v*UXgaBCp9~>D?IPtiXXw0wst!yG zSKOPyhW+eYJU|DFvltXGu7i{g@+9@_%vn-E4GmTh$Y}}~g%;dEb;IQvHW9GDDMQ*( z_R&2w#I^urg$EJkfD-<6deRtf>h@*g-wbI`>EZxM=_z2*RO~KV2G*-|5J>Dav9XixvW!Gh z)=(Yk7Sw<|If*Ln%}dvl@R4r@bPP-uDH|hkDA&|V4J47#9;t8^))650ADeJ>1I#l3 z-Ld^{^S5mxTuRCymCb^YT(MMiCxY8?6Vynexd3~+c~Kb8ww<3|b{m0agC15-Kcvw+ ziXC7N69ga03uow4pmY5SA`zVrAl7ixF(OgXhW&L_Z(7WmfGtuyXm8vqZOrnL{!yU7 zI;@hUG{}MNcWz+MiyJERrt0;kPQr>i7c(m>ov#V{6a1~(%9pn-A>XQG?P#m0wV`eG ztsQOExBmeh9GD_y&MBqDzFkd+xM+lnM!9H=i^jR=dM-MA9op?A&>-s@+)#KZYau$& z`(1>-UEQH|T$FV*?u@6)nwt(Z;v)3z>9Ur`o$*lC{dAzL5$Zs>+=jT^hPd1!Ty7CA zw+NS8gv%|$~7Ugn_a=Ash+@f4=Q7*SAms^y} zEym>*<8q5}xy88LVq9)9F1Hw$Ta3#s#^n~{a*K1h#kt(#TyAkLxA+2XH+K|R*2K6e zW*1?Dwlk#fQT1t3i?K1&nkHpD1C1(azzDXaM9QmK!wsOC0M#2#@6)dB$zIQ83c<3f zD|pw!?n?-bHA&A~F%V*1zq3%vG)=Vv?8enICKYLeaXl)gvHKIo(m9+-o1g(fQiJ%IxW literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.tap b/testgame/achievements.rpgdir/ohrrpgce.tap new file mode 100644 index 0000000000000000000000000000000000000000..6dc3cfa9466a85796b24e7a507f7563f30ec0529 GIT binary patch literal 80 LcmZQzpe_Ia08jt{ literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.til b/testgame/achievements.rpgdir/ohrrpgce.til new file mode 100644 index 0000000000000000000000000000000000000000..7a1592c055d5a3cfd107d1a0d0d49b08e78a925b GIT binary patch literal 64000 zcmeI*O|IfP5QbrP!j{gGSOSwC9UUoqLW&Rz$+)X30pk2XTrMO7FN5wI5`VEDJBhB8 zYIZyMwEFqePp2Q-v_EvG<6~`adHaC8bJ{=IY}-@YI{Kzf(*AXA+ccqh&1>>b#pC_$ z=XdB|dET&IBu@ZOMS17zdf9jFu{ksncRB~n@6f-n^%61O@%3hTGT!3(9nZ%L@=osg zNUz`KsMp)SxcP|Av+FmafBXJ^8kW~zzjpuf`~6t^(e+lCx4tSYczt*Fp7=ajnAyEp zZxG)c-rZL z?ROY&-EZTHV1fU%DcA2*ByYR+^_({vjz$0h1Q7T*0eetg>k-52&pV3`trxe%$og{) zjLWlf?1DV`-==ZB{aN$Q1$i1bsdv_Laeuw@B{*-|zr&%de@l5a`Az$$^~ojk@s4@^ z`stkxceb7b@_z62`*H$*{kdKqqyAl5ncho5p2m&xY}47lA9;8U_2<8G#}BDzUw5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5O4|9{`T0n z^^doEUCa2^=-bu{p!CXc)Qm%4KzXk0R#|uR{?#HUefw=dlp`t1F{EmnrB~m zOShKuBJ0KV3=w(#?;RiP_l_g-M!#1s<>FC(Rv#aiCvjFk9o4@^fBrnM9$UJ1h@9V; z@oM>RU;lf}^Z$DQx_s}~oBX_Ztlv@QL(1c$`q$ceDXPbo+tYo#0eOkv3-WY={PpbG zdOTj+_T_2)`7SW;@e@K%yy3n zJ00(}emX8s>Yb(FIWDjE@xGvcf%W26&mNr@biL{QrC)y<=(~Q$_ba=<=ycd8ByTVE z>_vIg>um^=ze4)gmFBJY!jji_XYU~|n=EO7U&4{SfnPRR(g44NBYWf9{IY>)fB*sr zAb0S4Tp*ptPy)A7h`|@Aq5`Ncm>~zK3M5|y z)>Xhz0AwcudBtGW!Z=i>09B=dRh9tB6rfrj1}7*VWV-@TKg?vfwNM5RgBwFWLn+Wj PDPTK6YD>U&qN@Y|LWdmE literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/ohrrpgce.veh b/testgame/achievements.rpgdir/ohrrpgce.veh new file mode 100644 index 0000000000000000000000000000000000000000..5e4254e9bc67f1a6b5b839a80146befded91d582 GIT binary patch literal 240 zcmd;aNli;E%_&iE%17JN-#+B@5j$oR fh{Jbf^U~DV;7$43Nqmq5v_N#?;+|I29q)qMZokpJnV|MIy1{LKGAMm`1( zCk`N46*7q%MXnxQz#)0lB9z@Eu;wMs@F(v1J^%W3|NEo={KWtL}P5=C2|NpdbeUB6d6fO@gM;Aw5A76MV zc#$xXt30B|O0eTtzW-;;|BBfEtmFU3?El;I|L^&UjG!9{1U(fBS|k~EH7t=vKde_$ zzh-99b$R53jP{eD|ERV9!pr~H-T&w6|MvHxoUjNB4iXt58z(b1J4H=MOJPt;by!P@ zVN0HAOR;xP!;DtbrC{H|ap=~4@#=}XtHB8p2No$2AVC}`TPj6qDr9#ldxklXmQJFm zV6VPxz0-Ng=Y-Svj^F>M@&DM&z0eO24mUhpv zedW-E`SGOx`^5kJ<^TTL(A^dg5HcksO*l4cOhkiVQkrvMuY_#Elz7yngyFQ5?ZTq= z(zf~H&-?7*{rB?b;OGk@3l1<25tUGs zkj($e<^SpL>+l#02{#!QQYap4I3$EgDxO*}wQeNMgBIVI6y~in?7~y?*K_ygn)>g) z{P*Jj|Nj600013G04#3+JCy)SyZ~C_2W|f)i~mEZ|6#}fh1UP5;{V0#|Jw5Z?)m>> C8;I}# literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/plotscr.lst b/testgame/achievements.rpgdir/plotscr.lst new file mode 100644 index 0000000000000000000000000000000000000000..673f13cd1ff5cfa5ae165c9bf6ffea254c90737d GIT binary patch literal 7720 zcma)>*V{Wq702g-3jqNe4?K@o{2N>#b^%2Y5U{~ycajXhZR|{vH?)-wf{GPT!LESx zUMwiRD@YNPDjk(#qxtwfGrQSrGBeA2@uq$9oinG++0Fl|E!m7Go5rHdb-uxTe{XiU zxesG+R9hImtcXH#?Db{NFUMZPx+9?>rwSor@4ByoQ*VL$Z?zSJSdu5yz@`^Ma_6Im}fk7=rrIf z;II@Jid^&V`s2&h+sS_fC1NU)b;7>$5^-E+1S-w7k9)Ct8{-q5D?SZa$JjsMKc!2V zjdX7Pqnwg|2UmX%QS${S_cz$5#`(-5e*0diwq}vYv?#;m^YGtT#v-t!vEw<)D`S?@ z^Tfa9XBYdGYATf+c-UuP-&n;vlK(<$KV7}ka3!dm{*fyko_Pv+m2gB%MN&FGZ{B(m z`;b{N(OxA>{uTKgO-iJej-@IRKG)KG{-Js|t=qFIW52gt&#H{wg?K;@>xNck>`tpUoFs{9y=eVT z1MjHbWBQeKG(~Wpq72rKuiN1l>T8x83!dn)3%m__)KB~LZY4i2IKSR4b{|-HGTJ2O z3eAJNdf?5~d&w`TkC^r=6dgKZqY$O+9Go{*I}pbsjgVO*3qDZ-{f1{Uz~s>DAo@ zV^^X-W0!zLJo5Ac`py-$kN0wy+x#1&pOFJ?-mET)*O%EmXt*KH?6Cu5m)dv?b4=sI zp+6p%*mI$myBK+8@>FS*Tt2j=w0`7V1P-gotcSZ0`-Pt1(HLa0E>v&o0_&Gv?tJtO z=;axj2DN(SQ9RG~mtO8%aB<0Jbiu#}`P7WZVtw}f1?q#kD6V}H_X%h>at`v;oP#~! z*_bC|K3DmWW+qo;?R#fo-Els_&=dxLj`*T9CR&8apP4*}@+=N>KehSNYvNCAee|06 zW1G*tCjQ8BO7xicL)bTz7}`^jO)z$(9vA)q@t}FP2V>udpX>RQP>&j8XWG6-%pl+J z&-iE9zShfq5Bo(v6Q|M7qddvi&S)~`yS4xNxbN8dCB6Dcjh(uu+xqS0POJMG^isZ2 z;)2TUnp5jO*~fjm)wBJ|IdwAjtyV9{eREaMQ%m1i)$?#)Z}o6GjPufC-`85bAa}~D zo@d>!uIhQXuXOW;CvudzBJ$;KzIeHl+k8fCMmTTW0Y0hC=OFi`B|X03*E?}ZFT|aI zI8 z?d);+#o=vy$v!axK5pLDn^~8;ZkAv}8*TjtxtX0` z8o$7m+9^4;^H480>FVL8m|IVG^}L+w?!z17vg;QwcW~QZ$S*j$O1>Qb=6TWft00$m z^}Od`*46WJX?woWy3JX*#wYE08RRx$jzArm`^t2iquJl;<3YAxnQQYR07^T@w%lN@ zl6?JYYK*!xH*-Q_Fk%Gz?EPmIB!{rC6qq?95d;P5lGe31*!+?)>aErbq7mX|=Cx=n z6I@rgtCz6>@)_6nn(m0A@p(ZG|5?2}({xLqG{URlUOZE|jNll2%J&=ENjdKssAAx-fJ9=oB^=|(pj>e-+Lz) zUv<09c8xUvtSW#xU4%@K%@AQmb_6&j5fnd500}a+3YU;H|7w9WLcYWT9yC^cJ{ePd zCp%HM1+{#b025#WOn?b60Vco%{*6G|@oHM?YT;XZ&CkXM*Pp5|co$7DF_gb&VZH8B dh8H|2+xt#Ov3D`Zcagb9x=y!7HO3D5_yV1LBS8QF literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/sfxdata.bin b/testgame/achievements.rpgdir/sfxdata.bin new file mode 100644 index 0000000000000000000000000000000000000000..16df5332aef4c9f996b808c7ea0c068e17ed0083 GIT binary patch literal 68 LcmZQzpd0`I07L)* literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/songdata.bin b/testgame/achievements.rpgdir/songdata.bin new file mode 100644 index 0000000000000000000000000000000000000000..00bc0474794e909101dcb2b0c7c809cfbe2bd665 GIT binary patch literal 128 LcmZQz7+3%R0Du4i literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/uicolors.bin b/testgame/achievements.rpgdir/uicolors.bin new file mode 100644 index 0000000000000000000000000000000000000000..e67594c528e66a0b307a5fe258ea109e9702c3f8 GIT binary patch literal 126 zcmZQzn7|OgkjL786N59u6(IeAfq~%;Q0600M1$cn!(E18hA4(@3~L!u z80Iq+GuSY+Fw9{{V#s7T%y5RmjlqfGCc`C$aE2g;9EKMRW(*|^1`G=r81X^?b(9*) literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/walkabouts.rgfx b/testgame/achievements.rpgdir/walkabouts.rgfx new file mode 100644 index 0000000000000000000000000000000000000000..c926310762200fa54e37aff4e0be73a620f528d0 GIT binary patch literal 21372 zcmeHOOKv4a5bXB*%+F?pgjm1<5ZnTGtl$Jj;~6Y546-eT1F&Jo1_}0X7q}G>nU%lp z^0PhTv0+}9>{s1UnN`&p74`9JfAz(epN~hwFnsd$FnoyLaX47x_Fx=`$GBY$n^!1a z1AdNtGaRyXJH9_Kf6(%WBTR7rXnZ*QiR;H&$rD`uhTp^OuUnK#S|izYAQP0gn!?nO zoY?YyLC20&0_oJlM%AL`u+N}`hmJol%z=$Wo z;O&51u)655mZ3`cS3A^~fWp(b@Wf~*p1_FEL)w8}Jx z4XK)@)M(-9>69lKufM_jW1r)#T3fUn6DHyy6hr+~Hpy(bknxW2Aomc{i-bo?j0OMjP^*K^_ zI@u$hgs3S`1Pf1Kz3~*0K8UM*y6~)gmM7Su6 zD>2r_#P+Q-Yng&L+#Qk!L0nO*Ss^DSBEEtNT86hO3ZY1LWQ6r)p4cw1tTJx4*}vq; z8BW4lur|FKR*3p+|A-=ck(8(s?6oQiSg_J0C1)UWHknmL&1i?Yjw18ZTsJ2LBf%-L z^&{}BN3Zdgj$Ww&yW|KjBQUgSCdk#QTHXcb4X3_fNVDk@F0NUUk{y_ol(dO^s)llM zdVOIlD_dLb61^ZpeNz*-M3q&+z|gxZR<^QLoXfB_A$J<>QlL;F&!7z61$N6kabc6h z=(c2v;}*uT*ed3!u<=x;N1?yLR3&Pbe<9hK~Z`6G2p9$%D%v) zFUb{`Y_}}1z!rac4|APZtc?=+>U5Ey=# zY7{$^bh)EfFje*+ougN6L++HQQqZ_dIth&>5N7N*d+Mo;x%|)rZWy#HG1Mk@Wn?!& zr6$mpL_0<&TUSofkWGS$&<0V#RMQj43Z_g?c}ITRm`NM$}Wgbr$4{VAmm~c`D4M{4IYE_WamI_gw zqnFu5&e$Ub5Ikj2t*WKbl)cHEh%7nFs>AvHdKfHIZ?wV!>0f^k++^m8%AiesIo3Ay zWjiRIvtNFWL47MvO4J1F?%rhv%H6}YFmG5Ps&n)*yT}^@0lbSu1uNJ#NJ@iSre;~D z5>vJjtgNUnSF1=p^>oRLPQaOC@n}3~N3Vn1IeICV(KWU+=hvY0v~G6Sm^Yj{z1nII zw-K<_){}#)ZJ_48H4KG@foVE}rr505?q#yFQA%6?3Q;S7>(ImMG?BL-^ebD%+0jd% z&pIf%k&-}z?wXTcyKlf#Fqb$9o;=(*K^s%foiA)qZHf%pMEoZhX!6R`7&bEuCG>5n z@H9Jmy#<~&>kYoo^&~A!)-WEkxLJ(h3K;|8#;{_Xjw+^`LcrbvE-<%=Wx`ZCh^1y-<#uY_DedB4OC~!$S3sWFz zN)*TwJ8uCO^h-&PUaR4Qk2ttj2M>_@3x^-e$?UCUqD;&qxcE8YyuUUH7dfalUI$S5E((<)S z)%nosVAbI}3hsrInk|f$Pkaq-`PHSl;o>aM>!pXD)aW{;=4_ABa&@hb>@9ntIeQ5& zqIB3fu4a`dM;*L7$1_b-3Nt_T?tK<*j>FBhxueCMy-g#hhDe^G#19cnPgEV%Fp77U z_6nVzexVg#v(&WFa_LvNPD&>lxuR5yZB$Nt7xl^Dj!?${im~zEQ(sS!ffa2&eGP3! zRlTc4xrmC7n2Gup z?T7k$%UNK5=y}usf#Bww+Ni5{wMZ9HvEeu_3ulT(e`&N>^-C?3`yt&TPW<`&=HguB zmj>J-KhDs*1-84?ocBY1jMn9;h!bC#++3WO*Ugeex(tGC`<9h6MWeqqTCDn|7RvpQ zZV@NGCcC*f7x|?Dx2&89hA9YtKen{|#fNdZY0|GohxS(L`gYz*?RSFb+G)yf#3g(>~10I?up_d;0XVZdbB=!{_Lj*Kc1YOpT7M5@%ayDrxz#ZCl}|B{PN+mv*Q=U o##iIF+h;G&UL0Snzd5})-$=p1>C?^Wi{s}f+ovbbemMT_Uo)KnU;qFB literal 0 HcmV?d00001 diff --git a/testgame/achievements.rpgdir/weapons.rgfx b/testgame/achievements.rpgdir/weapons.rgfx new file mode 100644 index 0000000000000000000000000000000000000000..765f494f7dfcc0cb0146577e5bcbe8503bd3730d GIT binary patch literal 1372 zcmeHHI}XAy47Ed&exN%CYBwgt1%jHCNR(Dd1dQB~Bd~D;HXJ|**3R+ZXTO(tc(S~1 zRy9@tPzrzt2S8wyfg!@3iW%UIfD?Ts6d;ZXX~*`U1+zWA28ajK6To^U74 Y;CdDK)&?(L0<*2Kz3Q}VwHej^39^hOGXMYp literal 0 HcmV?d00001 diff --git a/util.bas b/util.bas index 161c86513..12dc3f7a9 100644 --- a/util.bas +++ b/util.bas @@ -4451,3 +4451,12 @@ end function sub SmoothedTimer.stop_and_print() if stop() then ? tell() end sub + +'---------------------------------------------------------------------- +' other misc functions + +' Argument is a timeserial +FUNCTION format_date(timeser as double) as string + IF timeser = 0 THEN RETURN "0" + RETURN FORMAT(timeser, "yyyy mmm dd hh:mm:ss") +END FUNCTION diff --git a/util.bi b/util.bi index c00e268fa..bd1c6d2fb 100644 --- a/util.bi +++ b/util.bi @@ -866,6 +866,8 @@ declare function ends_with(s as string, suffix as string) as bool declare function readkey () as string +declare function format_date(timeser as double) as string + #macro debug_if_slow(starttime, seconds, extrainfo) IF TIMER > starttime + seconds THEN _ debuginfo __FUNCTION__ "(" & extrainfo & ") took " & CINT((TIMER - starttime) * 1000) & "ms" diff --git a/yetmore2.bas b/yetmore2.bas index 131f3423e..05b2ebd32 100644 --- a/yetmore2.bas +++ b/yetmore2.bas @@ -22,6 +22,8 @@ #include "menustuf.bi" #include "savegame.bi" #include "bmodsubs.bi" +#include "steam.bi" +#include "achievements.bi" Using Reload Using Reload.Ext @@ -275,6 +277,9 @@ killdir tmpdir, YES 'recursively deletes playing.tmp if it exists v_free modified_lumps restoremode + +Steam.uninitialize() + debuginfo "End." terminate_program errorout @@ -1066,6 +1071,10 @@ FUNCTION game_setoption(opt as string, arg as string) as integer terminate_program 10 RETURN 1 END IF + ELSEIF opt = "reset_platform_achievements" THEN + debug "Enqueuing platform achievement reset" + Achievements.definitions_reset + RETURN 1 END IF RETURN 0 END FUNCTION