QB64 Phoenix Edition
"Girl at Gulg Volcano" - Final Fantasy Tactics Proposition Calculator - Printable Version

+- QB64 Phoenix Edition (https://qb64phoenix.com/forum)
+-- Forum: QB64 Rising (https://qb64phoenix.com/forum/forumdisplay.php?fid=1)
+--- Forum: Code and Stuff (https://qb64phoenix.com/forum/forumdisplay.php?fid=3)
+---- Forum: Programs (https://qb64phoenix.com/forum/forumdisplay.php?fid=7)
+---- Thread: "Girl at Gulg Volcano" - Final Fantasy Tactics Proposition Calculator (/showthread.php?tid=2494)



"Girl at Gulg Volcano" - Final Fantasy Tactics Proposition Calculator - johannhowitzer - 03-08-2024

This program will require a little explanation.  It may look like a game when you run it, but it's just a way to plug in your stats and see what mission results you'll get.  Controls are just arrow keys, enter and escape, along with the plus/minus keys to the left of backspace, as an additional convenience when tweaking stats.  To compile, you will also need resource.mfi and icon32.ico, which are in the attached zip file:


.zip   Girl at Gulg Volcano.zip (Size: 210.3 KB / Downloads: 33)

Final Fantasy Tactics has little missions you can do, called "Propositions."  You pay a little money, send characters, they spend some days being unavailable while doing the mission, then you can return and pick them up when done, for some money, ability points, and also extra lore in many cases.

But success on a mission is not guaranteed, and Final Fantasy Tactics does not tell you anything about what influences the outcome.  Finally, years later via decompilation, the details were uncovered.  And they are ridiculously complicated for such a small part of the game.  Your characters' jobs, levels, and brave and faith values all play a part.

So in this program, you can select a proposition, set up the three characters to match your party, and you can see what the outcome will be at the bottom.  As the game allows you to send less than three characters, you can toggle each unit on or off.  And there is also a toggle between the original PS1 release's translation, and the PSP "War of the Lions" remake's translation.

This project represents FAR too much work, for how few people will probably ever use it for its intended purpose, but I thought I'd post it here anyway, both as a demonstration of the graphics, and just in case anyone happens to like Final Fantasy Tactics.  You do not want to know how much time I spent making sure the graphics were as faithful to the game as possible.  Was fun, though, and I learned a lot!

[Image: screenshot.png]

Code: (Select All)

$noprefix
$exeicon:'icon32.ico'

title "Girl at Gulg Volcano"

const true = -1
const false = 0

type coordinate_int
  x as integer
  y as integer
end type

' ===== Screen =====

const screenw = 256
const screenh = 240
dim shared full_screen as unsigned long ' Main screen before scaling
full_screen = newimage(screenw, screenh, 32)

dim shared option_window_size as byte
option_window_size = 2

dim shared scaled_screen(3) as unsigned long
scaled_screen(1) = newimage(280, 240, 32)
scaled_screen(2) = newimage(512, 480, 32)
scaled_screen(3) = newimage(768, 720, 32)
screen scaled_screen(option_window_size)
display_screen

' ===== Source images =====

dim shared bar_background as unsigned long
dim shared job_background as unsigned long
dim shared job_image      as unsigned long
dim shared window_image  as unsigned long
dim shared city_image    as unsigned long
dim shared data_image    as unsigned long

dim shared cursor_layer  as unsigned long
dim shared frame_assembly as unsigned long
dim shared store_screen  as unsigned long
dim shared final_screen  as unsigned long

cursor_layer    = newimage(screenw, screenh, 32)
frame_assembly  = newimage(screenw, screenh, 32)
store_screen    = newimage(screenw, screenh, 32)
final_screen    = newimage(screenw, screenh, 32)

' ===== Fonts =====

const fonts = 9

' Glass Fonts - custom pixel font processing and drawing

type font_structure
  image as unsigned long
  pos  as coordinate_int
  h    as integer
  w    as integer
end type

' Alignment in font calls
const left_align  = 0
const right_align  = 1
const center_align = 2

dim shared g_font(fonts, 255) as font_structure ' Number of fonts comes from main program header

' Current font options in use
dim shared font_using as byte          ' Index of current font being used
dim shared font_align as byte          ' Alignment
dim shared font_dest  as unsigned long  ' Destination image surface
dim shared font_x    as integer        ' Rolling font position, set by each glass_fonts call
dim shared font_y    as integer

const f_text_black  = 1
const f_text_red    = 2
const f_text_blue  = 3
const f_text_dkblue = 4
const f_gil_white  = 5
const f_map_white  = 6
const f_map_grey    = 7
const f_map_black  = 8
const f_map_dkblue  = 9

' --- Resource file ---

dim shared mfi_s(255) as long ' Size
dim shared mfi_o(255) as long ' Offset
dim shared mfi_count  as unsigned byte
dim shared mfi_index  as unsigned byte
mfi_loader "resource.mfi"

for n = 1 to fonts: initialize_font n: next

const f_row_h = 16

' ===== Window data =====

const win_normal      = 1 ' Normal window with drop shadow
const win_normal_dark = 2 ' Darkened version, when inactive
const win_list        = 3 ' Top and bottom bars added
const win_list_dark  = 4
const window_count    = 4

dim shared window_piece(window_count, 3) as coordinate_int
'          window            origin    left  top    mid  mid  right bot
set_window win_normal,        1,  1,    12,  8,    15,  15,    4,  6
set_window win_list,        44,  1,    3,  9,    15,  15,    5,  8
set_window win_normal_dark,  79,  1,    12,  8,    15,  15,    4,  6
set_window win_list_dark,  122,  1,    3,  9,    15,  15,    5,  8

' ===== Drop shadow data =====

source data_image

dim shared finger_shadow(-1 to 0, 16, 12) as integer
for d = -1 to 0: for y = 0 to 12: for x = 0 to 16
  dx = 1 + x + (18 * abs(d)): dy = 32 + y
  if alpha(point(dx, dy)) = 0 then continue
  finger_shadow(d, x, y) = red(point(dx, dy))
next x: next y: next d

dest full_screen
dim shared blob_shadow(-1 to 0, 19, 9) as integer
for d = -1 to 0: for y = 0 to 9: for x = 0 to 19
  dx = 1 + x + (21 * abs(d)): dy = 46 + y
  if alpha(point(dx, dy)) = 0 then continue
  blob_shadow(d, x, y) = red(point(dx, dy))
next x: next y: next d

' ===== Proposition system data =====

const psx = 1
const psp = 2

const failure      = 0
const success      = 59
const great_success = 99

const job_count  = 20
const prop_count = 96

dim shared job_name$(2, job_count)
const j_sq =  1: set_jn j_sq, "Squire",    "Squire"
const j_ch =  2: set_jn j_ch, "Chemist",    "Chemist"
const j_kn =  3: set_jn j_kn, "Knight",    "Knight"
const j_ar =  4: set_jn j_ar, "Archer",    "Archer"
const j_mk =  5: set_jn j_mk, "Monk",      "Monk"
const j_pr =  6: set_jn j_pr, "Priest",    "White Mage"
const j_wz =  7: set_jn j_wz, "Wizard",    "Black Mage"
const j_tm =  8: set_jn j_tm, "Time Mage",  "Time Mage"
const j_su =  9: set_jn j_su, "Summoner",  "Summoner"
const j_th = 10: set_jn j_th, "Thief",      "Thief"
const j_me = 11: set_jn j_me, "Mediator",  "Orator"
const j_or = 12: set_jn j_or, "Oracle",    "Mystic"
const j_ge = 13: set_jn j_ge, "Geomancer",  "Geomancer"
const j_ln = 14: set_jn j_ln, "Lancer",    "Dragoon"
const j_sm = 15: set_jn j_sm, "Samurai",    "Samurai"
const j_nj = 16: set_jn j_nj, "Ninja",      "Ninja"
const j_cl = 17: set_jn j_cl, "Calculator", "Arithmetician"
const j_bd = 18: set_jn j_bd, "Bard",      "Bard"
const j_dc = 19: set_jn j_dc, "Dancer",    "Dancer"
const j_mi = 20: set_jn j_mi, "Mime",      "Mime"

dim shared type_name$(8)
const salvage = 1: type_name$(salvage) = "Salvage"
const mining  = 2: type_name$(mining)  = "Mining"
const explore = 3: type_name$(explore) = "Exploration"
const combat  = 4: type_name$(combat)  = "Combat"
const invest  = 5: type_name$(invest)  = "Investigation"
const invest2 = 6: type_name$(invest2) = "Investigation 2"
const oddjob  = 7: type_name$(oddjob)  = "Odd Job"
const contest = 8: type_name$(contest) = "Contest"
dim shared type_job_wp(8, job_count) as byte ' WP per proptype-job pair
dim shared stat_name$(3)
const neutral = 0: stat_name$(neutral) = "Neutral"
const brave  = 1: stat_name$(brave)  = "Brave"
const faith  = 2: stat_name$(faith)  = "Faith"
dim shared stat_job_wp(3, job_count) as byte ' WP per propstat-job pair
dim shared stat_br_wp(3, 5)  as byte ' bonus WP from Brave stat
dim shared stat_fa_wp(3, 5)  as byte ' bonus WP from Faith stat
dim shared stat_lv_wp(3, 10) as byte ' bonus WP from level
set_prop_wp

dim shared stat_range$(5)
stat_range$(1) = "3-20"
stat_range$(2) = "21-40"
stat_range$(3) = "41-60"
stat_range$(4) = "61-80"
stat_range$(5) = "81-97"
dim shared level_range$(10)
level_range$(1)  = "1-10"
level_range$(2)  = "11-20"
level_range$(3)  = "21-30"
level_range$(4)  = "31-40"
level_range$(5)  = "41-50"
level_range$(6)  = "51-60"
level_range$(7)  = "61-70"
level_range$(8)  = "71-80"
level_range$(9)  = "81-90"
level_range$(10) = "91-99"

dim shared prop_name$(2, prop_count)
dim shared prop_city(prop_count) as byte
dim shared prop_type(prop_count) as byte
dim shared prop_stat(prop_count) as byte

dim shared city_name$(2, 15)
const igros    =  1: city_name$(psx, igros)    = "Igros Castle":            city_name$(psp, igros)    = "Eagrose Castle"
const gariland  =  2: city_name$(psx, gariland)  = "Gariland Magic City":      city_name$(psp, gariland)  = "Magick City of Gariland"
const dorter    =  3: city_name$(psx, dorter)    = "Dorter Trade City":        city_name$(psp, dorter)    = "Merchant City of Dorter"
const zaland    =  4: city_name$(psx, zaland)    = "Zaland Fort City":        city_name$(psp, zaland)    = "Castled City of Zaland"
const lionel    =  5: city_name$(psx, lionel)    = "Lionel Castle":            city_name$(psp, lionel)    = "Lionel Castle"
const goug      =  6: city_name$(psx, goug)      = "Goug Machine City":        city_name$(psp, goug)      = "Clockwork City of Goug"
const warjilis  =  7: city_name$(psx, warjilis)  = "Warjilis Trade City":      city_name$(psp, warjilis)  = "Port City of Warjilis"
const goland    =  8: city_name$(psx, goland)    = "Goland Coal City":        city_name$(psp, goland)    = "Mining Town of Gollund"
const lesalia  =  9: city_name$(psx, lesalia)  = "Lesalia Imperial Capital": city_name$(psp, lesalia)  = "Royal City of Lesalia"
const yardow    = 10: city_name$(psx, yardow)    = "Yardow Fort City":        city_name$(psp, yardow)    = "Walled City of Yardrow"
const bervenia  = 11: city_name$(psx, bervenia)  = "Bervenia Free City":      city_name$(psp, bervenia)  = "Free City of Bervenia"
const riovanes  = 12: city_name$(psx, riovanes)  = "Riovanes Castle":          city_name$(psp, riovanes)  = "Riovanes Castle"
const zeltennia = 13: city_name$(psx, zeltennia) = "Zeltennia Castle":        city_name$(psp, zeltennia) = "Zeltennia Castle"
const zarghidas = 14: city_name$(psx, zarghidas) = "Zarghidas Trade City":    city_name$(psp, zarghidas) = "Trade City of Sal Ghidos"
const limberry  = 15: city_name$(psx, limberry)  = "Limberry Castle":          city_name$(psp, limberry)  = "Limberry Castle"

dim shared chapter_name$(2, 9)
const ch2    = 1: chapter_name$(psx, ch2)    = "Chapter 2":          chapter_name$(psp, ch2)    = "Chapter 2"
const ch3    = 2: chapter_name$(psx, ch3)    = "Chapter 3":          chapter_name$(psp, ch3)    = "Chapter 3"
const ch3b  = 3: chapter_name$(psx, ch3b)  = "After Book Storage": chapter_name$(psp, ch3b)  = "After Book Storage"
const ch4    = 4: chapter_name$(psx, ch4)    = "Chapter 4":          chapter_name$(psp, ch4)    = "Chapter 4"
const ch4b  = 5: chapter_name$(psx, ch4b)  = "After Limberry":    chapter_name$(psp, ch4b)  = "After Limberry"
const virgo  = 6: chapter_name$(psx, virgo)  = "Aug 23 to Sep 22":  chapter_name$(psp, virgo)  = "During Virgo"
const aries  = 7: chapter_name$(psx, aries)  = "Mar 21 to Apr 19":  chapter_name$(psp, aries)  = "During Aries"
const sagit  = 8: chapter_name$(psx, sagit)  = "Nov 23 to Dec 22":  chapter_name$(psp, sagit)  = "During Sagittarius"
const cancer = 9: chapter_name$(psx, cancer) = "Jun 22 to Jul 22":  chapter_name$(psp, cancer) = "During Cancer"
dim shared prop_chapter(prop_count) as byte

const item = 1
const land = 2
dim shared prop_reward(prop_count) as byte
dim shared reward$(2, 2)
reward$(psx, item) = "Treasure"
reward$(psx, land) = "Unexplored Land"
reward$(psp, item) = "Artefact"
reward$(psp, land) = "Wonder"

' For setting up proposition data
dim shared prop_i as byte
dim shared prop_c as byte

set_prop_data

dim shared subtotal_wp    as integer ' Total before job is factored in
dim shared total_wp        as integer

' ===== Input handling =====

' References for press function and hold array
const up_key    = 1
const down_key  = 2
const left_key  = 3
const right_key = 4
const minus_key = 5
const plus_key  = 6
const f1_key    = 7 ' Toggle console
const enter_key = 8
const esc_key  = 9

' Input reference and binding data
const keybind_count = 9 ' Number of functions

' Input handling routine library - keyboard, gamepad, mouse

' Must be set per program: action constants, keybind_name$(), keybind_default(), keybind_overlap()

' Example of action constants:
' const up_key        =  1
' const down_key      =  2
' const left_key      =  3
' const right_key    =  4
' const weapon_key    =  5
' const shield_key    =  6


dim shared dev_keyboard as byte ' Store device index, to be re-checked whenever inputs are involved
dim shared dev_gamepad  as byte
dim shared dev_mouse    as byte
const keyboard = 1
const gamepad  = 2
const mouse    = 3

kc = keybind_count
dim shared keybind_name$(kc)                  ' Name of keybind slots - "WEAPON, UP" etc

dim shared key_name$(512, 2)                  ' Names of keyboard keys and gamepad buttons
dim shared keybind(kc, 2)          as integer ' Contains key code assigned by player
dim shared keybind_default(kc, 2)  as integer ' Defaults in case player wants to reset

dim shared axis_threshold as single ' Amount a stick needs to be tilted before input is registered
axis_threshold = 0.7
set_key_data

' Input tracking flags
dim shared press(kc)      as byte ' What was pressed this frame
dim shared hold(kc)        as byte ' What was pressed last frame


' Mouse data
dim shared mouse_press(3)    as byte
dim shared mouse_hold(3)      as byte
dim shared mouse_press_x(3)  as integer ' Records where mouse button press started
dim shared mouse_press_y(3)  as integer
dim shared mouse_release_x(3) as integer ' Records where mouse button was released
dim shared mouse_release_y(3) as integer
dim shared mouse_pos_x        as integer ' Current position
dim shared mouse_pos_y        as integer

set_keybinds

dim shared key_held(keybind_count) as byte
const repeat_key  = 10 ' Frames from starting to hold key when key begins repeating
const repeat_speed = 2  ' Once begun, repeat once per this many frames

' Cursor visual position
dim shared c_posx(3) as integer
dim shared c_posy(8) as integer
c_posx(1) =  5
c_posx(2) = 30
c_posx(3) = 55
c_posy(1) =  2
c_posy(2) =  4
c_posy(3) =  7
c_posy(4) = 17
c_posy(5) = 19
c_posy(6) = 20
c_posy(7) = 21
c_posy(8) = 22

const cmovef = 5 ' Frames it takes for cursor to move to new position

dim shared city_menu_scroll as byte
dim shared city_menu_cursor as byte
dim shared prop_menu_cursor as byte
city_menu_scroll = 0
city_menu_cursor = 1
prop_menu_cursor = 1

' ===== Ring job selection =====

const job_width  = 22
const job_height = 37

const interp_frames = 13

dim shared ring_pos(20, interp_frames) as coordinate_int ' Visual position of job icons on ring
ring_pos( 0, 0).x = 117: ring_pos( 0, 0).y = 168
ring_pos( 1, 0).x = 150: ring_pos( 1, 0).y = 164
ring_pos( 2, 0).x = 179: ring_pos( 2, 0).y = 154
ring_pos( 3, 0).x = 201: ring_pos( 3, 0).y = 140
ring_pos( 4, 0).x = 214: ring_pos( 4, 0).y = 121
ring_pos( 5, 0).x = 216: ring_pos( 5, 0).y = 102
ring_pos( 6, 0).x = 208: ring_pos( 6, 0).y =  83
ring_pos( 7, 0).x = 189: ring_pos( 7, 0).y =  66
ring_pos( 8, 0).x = 163: ring_pos( 8, 0).y =  54
ring_pos( 9, 0).x = 132: ring_pos( 9, 0).y =  48
ring_pos(10, 0).x =  99: ring_pos(10, 0).y =  48
ring_pos(11, 0).x =  68: ring_pos(11, 0).y =  55
ring_pos(12, 0).x =  43: ring_pos(12, 0).y =  67
ring_pos(13, 0).x =  25: ring_pos(13, 0).y =  84
ring_pos(14, 0).x =  17: ring_pos(14, 0).y = 103
ring_pos(15, 0).x =  20: ring_pos(15, 0).y = 122
ring_pos(16, 0).x =  33: ring_pos(16, 0).y = 140
ring_pos(17, 0).x =  55: ring_pos(17, 0).y = 155
ring_pos(18, 0).x =  84: ring_pos(18, 0).y = 164
ring_pos(19, 0).x =  0: ring_pos(19, 0).y =  0 ' These are to prevent invalid subscript, but still
ring_pos(20, 0).x = 233: ring_pos(20, 0).y =  0 '    position at extremes to show error visually
const ring_center_x = 116 ' Unit's current job in center of ring
const ring_center_y = 104

' Produce interpolation values for animation
i = 1 / interp_frames
for j1 = 0 to 18
  j2 = wrap(j1 + 1, 0, 18)
  dx = ring_pos(j2, 0).x - ring_pos(j1, 0).x
  dy = ring_pos(j2, 0).y - ring_pos(j1, 0).y
  for f = 1 to interp_frames
      ring_pos(j1, f).x = rounding(ring_pos(j1, 0).x + (dx * i * f))
      ring_pos(j1, f).y = rounding(ring_pos(j1, 0).y + (dy * i * f))
  next f
next j1

' ===== Cursor animation =====

dim shared cursor_anim(46) as byte
for n =  1 to  2: cursor_anim(n) =  0: next n
for n =  3 to  6: cursor_anim(n) =  1: next n
for n =  7 to 12: cursor_anim(n) =  2: next n
for n = 13 to 20: cursor_anim(n) =  1: next n
for n = 21 to 30: cursor_anim(n) =  0: next n
for n = 31 to 46: cursor_anim(n) = -1: next n
dim shared cursor_frame as byte
cursor_frame = 1

' ===== User settings =====

dim shared light_mode as byte
light_mode = 1

dim shared hue(2, 4)
const hue_back  = 0: hue(1, hue_back)  = 15: hue(2, hue_back)  =  0
const hue_full  = 1: hue(1, hue_full)  =  0: hue(2, hue_full)  = 15
const hue_faded  = 2: hue(1, hue_faded)  =  7: hue(2, hue_faded)  =  8
const hue_cursor = 3: hue(1, hue_cursor) =  1: hue(2, hue_cursor) =  9
const hue_red    = 4: hue(1, hue_red)    =  4: hue(2, hue_red)    = 12

dim shared console_type as byte
console_type = psx

dim shared proposition as integer ' Proposition currently being used
proposition = 1

type wp_structure
  job      as byte
  stat    as byte
  lv      as byte
  br      as byte
  fa      as byte
  subtotal as byte ' Total before job is factored in
  total    as byte
end type
type unit_structure
  disabled as byte ' True means unit is ignored in total
  job      as byte
  gender  as byte ' Only affects displayed sprite in ring, and availability of bard/dancer
  lv      as byte ' Values range 1 to 10
  br      as byte ' Values range 1 to 5
  fa      as byte ' Values range 1 to 5
  wp      as wp_structure ' Unit's current WP are processed into this
end type
dim shared unit(3) as unit_structure

const g_male  = 1
const g_female = 2

for u = 1 to 3
  unit(u).disabled = false
  unit(u).job      = j_sq  ' All jobs start as Squire
  unit(u).gender  = g_male
  unit(u).lv      = 1      ' All levels start as 1-10
  unit(u).br      = 3      ' All brave/faith starts at 41-60
  unit(u).fa      = 3
next u

calculate_wp






cx = 1
cy = 1

dim shared cursor_pos(3, 6) as coordinate_int

do
  limit 60

  putimage(0, 0)-(screenw - 1, screenh - 1), bar_background, full_screen, (0, 0)-(screenw - 1, screenh - 1)

  draw_prop_window false

  set_font f_text_red, left_align, full_screen
  ox = 165: oy = 10
  draw_frame win_normal, ox, oy, 80, 42, full_screen
  x1 = ox + 10: y1 = oy + 8
  cursor_pos(2, 1).x = x1 - 4: cursor_pos(2, 1).y = y1
  glass_fonts "Console", x1, y1
  if console_type = psx then t$ = "PSX" else t$ = "PSP"
  font_using = f_text_black: glass_fonts t$, x1 + 5, y1 + 16
  x1 = x1 + 44
  cursor_pos(3, 1).x = x1 - 4: cursor_pos(3, 1).y = y1
  font_using = f_text_red: glass_fonts "Size", x1, y1
  font_using = f_text_black: glass_fonts "x" + trim$(str$(option_window_size)), x1 + 3, y1 + 16

  for u = 1 to 3
      big_font = f_text_black: small_font = f_map_black: w = win_normal
      if unit(u).disabled = true then big_font = f_text_dkblue: small_font = f_map_dkblue: w = win_normal_dark

      set_font big_font, left_align, full_screen
      ox = 15 + ((u - 1) * 78): oy = 110
      for n = 3 to 6: cursor_pos(u, n).x = ox + 2: next n
      draw_frame w, ox, oy, 74, 90, full_screen
      x1 = ox + 6: x2 = x1 + 40: y1 = oy + 8
      glass_fonts "Job", x1, y1: cursor_pos(u, 3).y = y1: y1 = y1 + f_row_h
        set_font small_font, right_align, full_screen
        glass_fonts_backtrack level_range$(unit(u).lv), x2, y1 + 1, 1
        set_font big_font, left_align, full_screen
      glass_fonts "Lv",  x1, y1: cursor_pos(u, 4).y = y1: y1 = y1 + f_row_h
        set_font small_font, right_align, full_screen
        glass_fonts_backtrack stat_range$(unit(u).br), x2, y1 + 1, 1
        set_font big_font, left_align, full_screen
      glass_fonts "Br",  x1, y1: cursor_pos(u, 5).y = y1: y1 = y1 + f_row_h
        set_font small_font, right_align, full_screen
        glass_fonts_backtrack stat_range$(unit(u).fa), x2, y1 + 1, 1
        set_font big_font, left_align, full_screen
      glass_fonts "Fa",  x1, y1: cursor_pos(u, 6).y = y1: y1 = y1 + f_row_h
      glass_fonts "Total",                          x1, y1:                          y1 = y1 + f_row_h

      font_align = right_align
      x1 = ox + 65: y1 = oy + 8
      glass_fonts str$(unit(u).wp.job + unit(u).wp.stat), x1, y1: y1 = y1 + f_row_h
      glass_fonts str$(unit(u).wp.lv),                    x1, y1: y1 = y1 + f_row_h
      glass_fonts str$(unit(u).wp.br),                    x1, y1: y1 = y1 + f_row_h
      glass_fonts str$(unit(u).wp.fa),                    x1, y1: y1 = y1 + f_row_h
      if unit(u).disabled = false then font_using = f_text_blue
      glass_fonts str$(unit(u).wp.total),                x1, y1: y1 = y1 + f_row_h

      draw_job_icon unit(u).job, unit(u).gender, unit(u).disabled, ox + 26, oy - 18, full_screen
      cursor_pos(u, 2).x = ox + 23
      cursor_pos(u, 2).y = oy -  7
  next u

  x1 = 154: y1 = 214
  set_font f_map_white, left_align, full_screen
  glass_fonts_backtrack "Total Work Points", 29, y1, 1

  if total_wp < 59 then font_using = f_map_white else font_using = f_map_grey
  glass_fonts_backtrack "0-58 Failure", x1 + 5, y1 - 10, 1
  if total_wp => 59 and total_wp < 99 then font_using = f_map_white else font_using = f_map_grey
  glass_fonts_backtrack "59-98 Success", x1, y1, 1
  if total_wp => 99 then font_using = f_map_white else font_using = f_map_grey
  glass_fonts_backtrack "99+ Great Success", x1 + 9, y1 + 10, 1

  set_font f_gil_white, center_align, full_screen
  glass_fonts trim$(str$(total_wp)), inthalf(screenw) - 1, y1 - 2

  draw_cursor cursor_pos(cx, cy).x, cursor_pos(cx, cy).y - 1, false

  display_screen
  update_inputs_repeat

  if new_press_repeat(left_key)  = true then cx = wrap(cx - 1, 1, 3)
  if new_press_repeat(right_key) = true then cx = wrap(cx + 1, 1, 3)
  if new_press_repeat(up_key)    = true then
      cy = wrap(cy - 1, 1, 6)
      if cy = 1 or cy = 6 then cx = 1
  end if
  if new_press_repeat(down_key)  = true then
      cy = wrap(cy + 1, 1, 6)
      if cy = 1 or cy = 2 then cx = 1
  end if

  d = 0
  if new_press_repeat(minus_key) = true then d = -1
  if new_press_repeat(plus_key)  = true then d =  1
  if new_press_repeat(enter_key) = true and cy <> 3 and cx + cy > 2 then d = 1

  select case cy
      case 1
        if cx = 2 then console_type      = wrap(console_type + d, psx, psp)
        if cx = 3 then option_window_size = wrap(option_window_size + d, 1, 3): screen scaled_screen(option_window_size)
      case 2: unit(cx).disabled = wrap(unit(cx).disabled + d, true, false)
      case 3: unit(cx).job      = wrap(unit(cx).job      + d, 1, 20)
      case 4: unit(cx).lv      = wrap(unit(cx).lv      + d, 1, 10)
      case 5: unit(cx).br      = wrap(unit(cx).br      + d, 1,  5)
      case 6: unit(cx).fa      = wrap(unit(cx).fa      + d, 1,  5)
  end select

  if d <> 0 then calculate_wp

  if new_press(enter_key) = true and cx + cy = 2 then
      draw_prop_window true
      draw_cursor cursor_pos(cx, cy).x, cursor_pos(cx, cy).y - 1, true
      capture_screen
      prop_menu
      calculate_wp
  end if
  if new_press(enter_key) = true and cy = 3 then
      job_menu cx
      calculate_wp
  end if

loop

system





sub draw_prop_window(dark)

ox = 15: oy = 10
if dark = false then w = win_normal:      set_font f_text_black, left_align, full_screen
if dark = true  then w = win_normal_dark: set_font f_text_dkblue, left_align, full_screen
draw_frame w, ox, oy, 130, 90, full_screen
x1 = ox + 6: y1 = oy + 8
cursor_pos(1, 1).x = ox + 2: cursor_pos(1, 1).y = y1
glass_fonts prop_name$(console_type, proposition), x1, y1: y1 = y1 + f_row_h
if dark = false then font_using = f_text_red
glass_fonts "When",  x1, y1: y1 = y1 + f_row_h
glass_fonts "Type",  x1, y1: y1 = y1 + f_row_h
glass_fonts "Stat",  x1, y1: y1 = y1 + f_row_h
glass_fonts "Reward", x1, y1

if dark = false then font_using = f_text_black
x1 = ox + 44: y1 = oy + 8
y1 = y1 + f_row_h
glass_fonts chapter_name$(console_type, prop_chapter(proposition)), x1, y1: y1 = y1 + f_row_h
glass_fonts type_name$(prop_type(proposition)),                    x1, y1: y1 = y1 + f_row_h
glass_fonts stat_name$(prop_stat(proposition)),                    x1, y1: y1 = y1 + f_row_h
if dark = false then font_using = f_text_blue
glass_fonts reward$(console_type, prop_reward(proposition)),        x1, y1

w = 95: h = 9
x1 = 1 + ((w + 2) * 2 * abs(dark)) + ((w + 2) * (console_type - 1))
y1 = 1 + ((h + 2) * (prop_city(proposition) - 1))
putimage(ox + 2, oy - 4)-step(w, h), city_image, full_screen, (x1, y1)-step(w, h)

end sub


sub draw_city_menu(dark, ox, oy)

if dark = false then w = win_list else w = win_list_dark
draw_frame w, ox, oy, 118, 130, full_screen
x1 = ox + 6: y1 = oy + 13

for n = 1 to 7
  set_font f_text_dkblue, left_align, full_screen
  if dark = false then font_using = f_text_black

  glass_fonts city_name$(console_type, n + city_menu_scroll), x1, y1
  if n = city_menu_cursor - city_menu_scroll and dark = false then draw_cursor ox + 2, y1 - 1, dark
  y1 = y1 + f_row_h
next n

set_font f_map_white, left_align, full_screen
if dark = true then font_using = f_map_grey
glass_fonts_backtrack "City", ox + 2, oy - 4, 1

if dark = true then exit sub

' Scroll arrows
w = 7: h = 15
if city_menu_scroll > 0 then putimage(ox + 111, oy +  13)-step(w, h), data_image, full_screen, (1,        1)-step(w, h)
if city_menu_scroll < 8 then putimage(ox + 111, oy + 104)-step(w, h), data_image, full_screen, (1 + w + 2, 1)-step(w, h)
w = 7: h = 6
putimage(ox + 111, oy + 21 + (city_menu_cursor * 5))-step(w, h), data_image, full_screen, (19, 1)-step(w, h)

end sub


sub prop_menu

city_menu_cursor = prop_city(proposition)
city_ox = 35
city_oy = 30

dim prop_list(8) as byte
dim prop_list_count

update_inputs_repeat

' Select city
do
  limit 60

  city_menu_scroll = min(max(city_menu_scroll, city_menu_cursor - 7), city_menu_cursor - 1)

  restore_screen
  draw_city_menu false, city_ox, city_oy

  display_screen
  update_inputs_repeat

  if new_press_repeat(up_key)  = true then city_menu_cursor = max(city_menu_cursor - 1, 1)
  if new_press_repeat(down_key) = true then city_menu_cursor = min(city_menu_cursor + 1, 15)

  if new_press(esc_key)  = true then exit sub
  if new_press(enter_key) = false then continue

  ' Compile proposition list
  prop_list_count = 0
  for p = 1 to prop_count
      if prop_city(p) <> city_menu_cursor then continue
      prop_list_count = prop_list_count + 1
      prop_list(prop_list_count) = p
  next p
  ' Get dynamic window width from widest text line
  max_width = 0
  for p = 1 to prop_list_count
      max_width = max(max_width, text_width(prop_name$(console_type, prop_list(p)), f_text_black))
  next p

  restore_screen
  draw_city_menu true, city_ox, city_oy
  capture_screen

  ' Select proposition
  prop_menu_cursor = 1
  update_inputs_repeat

  do
      limit 60

      restore_screen
      draw_cursor city_ox + 2, city_oy + 13 + (((city_menu_cursor - city_menu_scroll) - 1) * f_row_h) - 1, true

      ox = 55: oy = 50
      draw_frame win_list, ox, oy, max_width + 15, (prop_list_count * f_row_h) + 18, full_screen
      x1 = ox + 6: y1 = oy + 13

      for n = 1 to prop_list_count
        set_font f_text_black, left_align, full_screen
        glass_fonts prop_name$(console_type, prop_list(n)), x1, y1
        if n = prop_menu_cursor then draw_cursor ox + 2, y1 - 1, false
        y1 = y1 + f_row_h
      next n

      set_font f_map_white, left_align, full_screen
      if console_type = psx then t$ = "Proposition" else t$ = "Errand"
      glass_fonts_backtrack t$, ox + 2, oy - 4, 1

      display_screen
      update_inputs_repeat

      if new_press_repeat(up_key)    = true then prop_menu_cursor = wrap(prop_menu_cursor - 1, 1, prop_list_count)
      if new_press_repeat(down_key)  = true then prop_menu_cursor = wrap(prop_menu_cursor + 1, 1, prop_list_count)

      if new_press(esc_key)  = true then exit do
      if new_press(enter_key) = true then proposition = prop_list(prop_menu_cursor): exit sub
  loop
loop

end sub


sub job_menu(u)

' Set of references to jobs on ring, since bard and dancer must occupy the same position
temp_g = unit(u).gender
dim ring_ref(19) as byte
dim wp_preview$(19)

for n = 1 to 19
  n1 = n
  if n = 19 then n1 = 20
  if n = 18 and temp_g = g_female then n1 = j_dc
  ring_ref(n) = n1
  wp_preview$(n) = trim$(str$(type_job_wp(prop_type(proposition), n1) + stat_job_wp(prop_stat(proposition), n1)))
next n

' Using current job, find initial cursor position
for n = 1 to 19
  if unit(u).job = ring_ref(n) then j = n
next n
if j = false then j = j_sq ' Prevent somehow failing, such as having male dancer or female bard

update_inputs_repeat
wp_dx =  10 ' Position of work point text above each job
wp_dy = -13

do
  limit 60

  putimage(0, 0)-(screenw - 1, screenh - 1), job_background, full_screen, (0, 0)-(screenw - 1, screenh - 1)

  set_font f_map_white, center_align, full_screen
  for n = 9 to 0 step -1
      p = wrap(-n, 0, 18)
      draw_job_icon ring_ref(wrap(j - n, 1, 19)), temp_g, false, ring_pos(p, 0).x, ring_pos(p, 0).y, full_screen
      glass_fonts_backtrack wp_preview$(wrap(j - n, 1, 19)), ring_pos(p, 0).x + wp_dx, ring_pos(p, 0).y + wp_dy, 1
      if n = 0 then continue
      draw_job_icon ring_ref(wrap(j + n, 1, 19)), temp_g, false, ring_pos(n, 0).x, ring_pos(n, 0).y, full_screen
      glass_fonts_backtrack wp_preview$(wrap(j + n, 1, 19)), ring_pos(n, 0).x + wp_dx, ring_pos(n, 0).y + wp_dy, 1
  next n
  draw_job_icon unit(u).job, unit(u).gender, false, ring_center_x, ring_center_y, full_screen
  glass_fonts_backtrack wp_preview$(unit(u).job), ring_center_x + wp_dx, ring_center_y + wp_dy, 1

  draw_frame win_normal, 83, 207, 90, 27, full_screen
  set_font f_text_black, center_align, full_screen
  glass_fonts job_name$(console_type, ring_ref(j)), inthalf(screenw), 217
  putimage(3, 224)-(80, 236), data_image, full_screen, (1, 18)-(78, 30) ' Gender UI

  display_screen
  update_inputs_repeat

  if press(left_key) = true then
      for f = 1 to interp_frames
        limit 60
        putimage(0, 0)-(screenw - 1, screenh - 1), job_background, full_screen, (0, 0)-(screenw - 1, screenh - 1)
        set_font f_map_white, center_align, full_screen
        for n = 9 to 0 step -1
            p = wrap(-n, 0, 18)
            draw_job_icon ring_ref(wrap(j - n, 1, 19)), temp_g, false, ring_pos(p, f).x, ring_pos(p, f).y, full_screen
            glass_fonts_backtrack wp_preview$(wrap(j - n, 1, 19)), ring_pos(p, f).x + wp_dx, ring_pos(p, f).y + wp_dy, 1
            if n = 0 then continue
            draw_job_icon ring_ref(wrap(j + n, 1, 19)), temp_g, false, ring_pos(n, f).x, ring_pos(n, f).y, full_screen
            glass_fonts_backtrack wp_preview$(wrap(j + n, 1, 19)), ring_pos(n, f).x + wp_dx, ring_pos(n, f).y + wp_dy, 1
        next n
        draw_job_icon unit(u).job, unit(u).gender, false, ring_center_x, ring_center_y, full_screen
        glass_fonts_backtrack wp_preview$(unit(u).job), ring_center_x + wp_dx, ring_center_y + wp_dy, 1

        draw_frame win_normal, 83, 207, 90, 27, full_screen
        set_font f_text_black, center_align, full_screen
        glass_fonts job_name$(console_type, ring_ref(j)), inthalf(screenw), 217
        putimage(3, 224)-(80, 236), data_image, full_screen, (1, 18)-(78, 30) ' Gender UI
        display_screen
      next f
      j = wrap(j - 1, 1, job_count - 1)
  end if

  if press(right_key) = true then
      j = wrap(j + 1, 1, job_count - 1)
      for f = interp_frames to 1 step -1
        limit 60
        putimage(0, 0)-(screenw - 1, screenh - 1), job_background, full_screen, (0, 0)-(screenw - 1, screenh - 1)
        set_font f_map_white, center_align, full_screen
        for n = 9 to 0 step -1
            p = wrap(-n, 0, 18)
            draw_job_icon ring_ref(wrap(j - n, 1, 19)), temp_g, false, ring_pos(p, f).x, ring_pos(p, f).y, full_screen
            glass_fonts_backtrack wp_preview$(wrap(j - n, 1, 19)), ring_pos(p, f).x + wp_dx, ring_pos(p, f).y + wp_dy, 1
            if n = 0 then continue
            draw_job_icon ring_ref(wrap(j + n, 1, 19)), temp_g, false, ring_pos(n, f).x, ring_pos(n, f).y, full_screen
            glass_fonts_backtrack wp_preview$(wrap(j + n, 1, 19)), ring_pos(n, f).x + wp_dx, ring_pos(n, f).y + wp_dy, 1
        next n
        draw_job_icon unit(u).job, unit(u).gender, false, ring_center_x, ring_center_y, full_screen
        glass_fonts_backtrack wp_preview$(unit(u).job), ring_center_x + wp_dx, ring_center_y + wp_dy, 1

        draw_frame win_normal, 83, 207, 90, 27, full_screen
        set_font f_text_black, center_align, full_screen
        glass_fonts job_name$(console_type, ring_ref(wrap(j - 1, 1, job_count - 1))), inthalf(screenw), 217
        putimage(3, 224)-(80, 236), data_image, full_screen, (1, 18)-(78, 30) ' Gender UI
        display_screen
      next f
  end if

  if new_press(up_key) = true or new_press(down_key) = true then
      temp_g = toggle(temp_g, g_male, g_female)
      if temp_g = g_male then ring_ref(18) = j_bd else ring_ref(18) = j_dc
      wp_preview$(18) = trim$(str$(type_job_wp(prop_type(proposition), ring_ref(18)) + stat_job_wp(prop_stat(proposition), ring_ref(18))))
  end if

  if new_press(esc_key)  = true then exit sub
  if new_press(enter_key) = true then exit do
loop

unit(u).job    = ring_ref(j)
unit(u).gender = temp_g

end sub


sub draw_job_icon(j, gender, dark, x, y, d~&)
' j is job, dark is true if dark version should be used

' Blob shadow
preserve& = dest
dest full_screen
source full_screen

for y1 = 0 to 9: for x1 = 0 to 19
  v = blob_shadow(dark, x1, y1)
  if v = 0 then continue
  shx = x + x1 - 1: shy = y + y1 + 28
  pset(shx, shy), rgba32(max(0, red(point(shx, shy)) - v), max(0, green(point(shx, shy)) - v), max(0, blue(point(shx, shy)) - v), 255)
next x1: next y1

dest preserve&

x1 = 1 + ((j - 1) * (job_width  + 2))
y1 = 1 + ((gender - 1) * (job_height + 2)) + ((2 * (job_height + 2)) * abs(dark))
putimage(x, y)-step(job_width, job_height), job_image, d~&, (x1, y1)-step(job_width, job_height)

end sub


sub draw_cursor(x, y, dark)

if dark = false then set_font f_text_black,  right_align, cursor_layer: dx = cursor_anim((timer * 60) mod 46)
if dark = true  then set_font f_text_dkblue, right_align, full_screen:  dx = -1
glass_fonts "@", x + dx, y

preserve& = dest
dest full_screen
source full_screen

for y1 = 0 to 12: for x1 = 0 to 16
  v = finger_shadow(dark, x1, y1)
  if v = 0 then continue
  shx = x + dx + x1 - 14: shy = y + y1
  pset(shx, shy), rgba32(max(0, red(point(shx, shy)) - v), max(0, green(point(shx, shy)) - v), max(0, blue(point(shx, shy)) - v), 255)
next x1: next y1

dest preserve&

end sub


sub calculate_wp

subtotal_wp = 0
total_wp    = 0

for u = 1 to 3
  unit(u).wp.job      = 0
  unit(u).wp.stat    = 0
  unit(u).wp.lv      = 0
  unit(u).wp.br      = 0
  unit(u).wp.fa      = 0
  unit(u).wp.subtotal = 0
  unit(u).wp.total    = 0

  j = unit(u).job
  if j <> false then
      s = prop_stat(proposition)
      unit(u).wp.job      = type_job_wp(prop_type(proposition), j)
      unit(u).wp.stat    = stat_job_wp(s, j)
      unit(u).wp.lv      = stat_lv_wp(s, unit(u).lv)
      unit(u).wp.br      = stat_br_wp(s, unit(u).br)
      unit(u).wp.fa      = stat_fa_wp(s, unit(u).fa)
      unit(u).wp.subtotal = unit(u).wp.lv + unit(u).wp.br + unit(u).wp.fa
      unit(u).wp.total    = unit(u).wp.subtotal + unit(u).wp.job + unit(u).wp.stat
  end if

  if unit(u).disabled = false then
      subtotal_wp = subtotal_wp + unit(u).wp.subtotal
      total_wp    = total_wp    + unit(u).wp.total
  end if
next u

end sub


function right_num$(v, w)
t$ = trim$(str$(v))
right_num$ = space$(max(w - len(t$), 0)) + t$
end function


sub draw_frame(f, ox, oy, win_w, win_h, d~&)

preserve& = dest
dest frame_assembly
dontblend

' Piece dimensions
w1 = window_piece(f, 1).x: h1 = window_piece(f, 1).y
w2 = window_piece(f, 2).x: h2 = window_piece(f, 2).y
w3 = window_piece(f, 3).x: h3 = window_piece(f, 3).y

' Required size of center bands based on size of edges
center_w = win_w - w1 - w3 - 2
center_h = win_h - h1 - h3 - 2
center_cols = int(center_w / max(1, w2 + 1)) + 1 ' Overdrawing by one is fine

' Top corners and edge
draw_winp f, 0, 0, 1, 1
x = w1 + 1
for n = 1 to center_cols
  draw_winp f, x, 0, 2, 1
  x = x + w2 + 1
next n
draw_winp f, win_w - 1 - w3, 0, 3, 1

' Middle row
y = h1 + 1
for row = 1 to int(center_h / max(1, h2 + 1)) + 1
  draw_winp f, 0, y, 1, 2
  x = w1 + 1
  for n = 1 to center_cols
      draw_winp f, x, y, 2, 2
      x = x + w2 + 1
  next n
  draw_winp f, win_w - 1 - w3, y, 3, 2
  y = y + h2 + 1
next row

' Bottom row
y = win_h - 1 - h3
draw_winp f, 0, y, 1, 3
x = w1 + 1
for n = 1 to center_cols
  draw_winp f, x, y, 2, 3
  x = x + w2 + 1
next n
draw_winp f, win_w - 1 - w3, y, 3, 3

blend
dest preserve&

' Copy finished window to screen
putimage(ox, oy)-step(win_w - 1, win_h - 1), frame_assembly, d~&, (0, 0)-step(win_w - 1, win_h - 1)

end sub


sub draw_winp(f, x, y, x2, y2)

dim x(3): dim y(3)
' Source image coordinates
x(1) =        window_piece(f, 0).x:    y(1) =        window_piece(f, 0).y
x(2) = x(1) + window_piece(f, 1).x + 2: y(2) = y(1) + window_piece(f, 1).y + 2
x(3) = x(2) + window_piece(f, 2).x + 2: y(3) = y(2) + window_piece(f, 2).y + 2

w = window_piece(f, x2).x: h = window_piece(f, y2).y
line(x, y)-step(w, h), rgba32(0, 0, 0, 0), bf
putimage(x, y)-step(w, h), window_image, frame_assembly, (x(x2), y(y2))-step(w, h)

end sub


sub set_window(w, x0, y0, w1, h1, w2, h2, w3, h3)
window_piece(w, 0).x = x0
window_piece(w, 0).y = y0
window_piece(w, 1).x = w1
window_piece(w, 1).y = h1
window_piece(w, 2).x = w2
window_piece(w, 2).y = h2
window_piece(w, 3).x = w3
window_piece(w, 3).y = h3
end sub


sub display_screen

putimage(0, 0)-(screenw - 1, screenh - 1), full_screen,  final_screen, (0, 0)-(screenw - 1, screenh - 1)
putimage(0, 0)-(screenw - 1, screenh - 1), cursor_layer, final_screen, (0, 0)-(screenw - 1, screenh - 1)
clear_image cursor_layer
hardware_image = copyimage(final_screen, 33)
putimage(0, 0)-((screenw * option_window_size) - 1, (screenh * option_window_size) - 1), hardware_image
display
freeimage hardware_image

end sub


sub clear_image(d&)
preserve& = dest
dest d&
cls , rgba32(0, 0, 0, 0)
dest preserve&
end sub


sub capture_screen
clear_image store_screen
putimage(0, 0)-(screenw - 1, screenh - 1), full_screen, store_screen, (0, 0)-(screenw - 1, screenh - 1)
end sub


sub restore_screen
clear_image full_screen
putimage(0, 0)-(screenw - 1, screenh - 1), store_screen, full_screen, (0, 0)-(screenw - 1, screenh - 1)
end sub


sub glass_fonts_backtrack(t1$, x1, y1, backtrack)
' Special version, last parameter is number of pixels to backtrack per character

t$ = t1$
carriage = true
if right$(t$, 1) = ";" then
  carriage = false
  t$ = left$(t$, len(t$) - 1)
end if

x = x1: y = y1
f = font_using

if font_align <> left_align then
  ' Adjust starting point based on line width, for center or right align
  w = text_width(t$, f) - (len(t$) * backtrack)
  if font_align = center_align then w = int(w * 0.5)
  x = x - w
end if

for n = 1 to len(t$)
  c = asc(mid$(t$, n, 1))
  w = g_font(f, c).w
  putimage(x, y)-step(w, g_font(f, 0).h), g_font(f, 0).image, font_dest, (g_font(f, c).pos.x, g_font(f, c).pos.y)-step(w, g_font(f, 0).h)
  x = x + w + 1 - backtrack
next n

font_x = x1
font_y = y1
if carriage = false then font_x = x
if carriage = true  then font_y = y1 + g_font(f, 0).h

end sub


sub update_inputs_repeat

update_inputs
for b = 1 to keybind_count
  key_held(b) = key_held(b) + 1
  if hold(b) = false then key_held(b) = 0
  if key_held(b) > repeat_key then key_held(b) = repeat_key - repeat_speed
next b

end sub


function new_press_repeat(b)
new_press_repeat = false
if new_press(b) = true or key_held(b) => repeat_key then new_press_repeat = true
end function


sub mfi_loader(f$)

mfi = freefile

open f$ for binary as #mfi
get #mfi, , mfi_count
for i = 1 to mfi_count
  get #mfi, , mfi_o(i)
  get #mfi, , mfi_s(i)
  mfi_o(i) = mfi_o(i) + 1
next i

mfi_index = 1

' ----- Images -----

g_font(f_text_black,  0).image = load_gfx(mfi)
g_font(f_text_red,    0).image = load_gfx(mfi)
g_font(f_text_blue,  0).image = load_gfx(mfi)
g_font(f_text_dkblue, 0).image = load_gfx(mfi)
g_font(f_gil_white,  0).image = load_gfx(mfi)
g_font(f_map_white,  0).image = load_gfx(mfi)
g_font(f_map_grey,    0).image = load_gfx(mfi)
g_font(f_map_black,  0).image = load_gfx(mfi)
g_font(f_map_dkblue,  0).image = load_gfx(mfi)

bar_background = load_gfx(mfi)
job_background = load_gfx(mfi)
job_image      = load_gfx(mfi)
window_image  = load_gfx(mfi)
city_image    = load_gfx(mfi)
data_image    = load_gfx(mfi)

if fileexists("mfi_temp.dat") then kill "mfi_temp.dat"

end sub


function load_gfx&(mfi)

if fileexists("mfi_temp.dat") then kill "mfi_temp.dat"
mfidata = freefile

open "mfi_temp.dat" for binary as #mfidata
dat$ = space$(mfi_s(mfi_index))
get #mfi, mfi_o(mfi_index), dat$
put #mfidata, , dat$

close #mfidata
load_gfx& = loadimage("mfi_temp.dat", 32)

mfi_index = mfi_index + 1

end function


function load_sfx&(mfi)

if fileexists("mfi_temp.dat") then kill "mfi_temp.dat"
mfidata = freefile

open "mfi_temp.dat" for binary as #mfidata
dat$ = space$(mfi_s(mfi_index))
get #mfi, mfi_o(mfi_index), dat$
put #mfidata, , dat$

close #mfidata
load_sfx& = sndopen("mfi_temp.dat")

mfi_index = mfi_index + 1

end function


sub set_keybinds

' Default keybinds
d = keyboard
keybind_name$(up_key)    = "UP":    keybind_default(up_key,    d) = 329
keybind_name$(down_key)  = "DOWN":  keybind_default(down_key,  d) = 337
keybind_name$(left_key)  = "LEFT":  keybind_default(left_key,  d) = 332
keybind_name$(right_key) = "RIGHT": keybind_default(right_key,  d) = 334
keybind_name$(minus_key) = "MINUS": keybind_default(minus_key,  d) = 13 ' Near backspace, not on numpad
keybind_name$(plus_key)  = "PLUS":  keybind_default(plus_key,  d) = 14
keybind_name$(f1_key)    = "F1":    keybind_default(f1_key,    d) = 60
keybind_name$(enter_key) = "ENTER": keybind_default(enter_key,  d) = 29
keybind_name$(esc_key)  = "ESC":  keybind_default(esc_key,    d) = 2

' On launch, start with defaults
set_default_keybinds

end sub


sub set_type_job_wp(t, jsq, jch, jkn, jar, jmk, jpr, jwz, jtm, jsu, jth, jme, jor, jge, jln, jsm, jnj, jcl, jbd, jdc, jmi)

type_job_wp(t, j_sq) = jsq
type_job_wp(t, j_ch) = jch
type_job_wp(t, j_kn) = jkn
type_job_wp(t, j_ar) = jar
type_job_wp(t, j_mk) = jmk
type_job_wp(t, j_pr) = jpr
type_job_wp(t, j_wz) = jwz
type_job_wp(t, j_tm) = jtm
type_job_wp(t, j_su) = jsu
type_job_wp(t, j_th) = jth
type_job_wp(t, j_me) = jme
type_job_wp(t, j_or) = jor
type_job_wp(t, j_ge) = jge
type_job_wp(t, j_ln) = jln
type_job_wp(t, j_sm) = jsm
type_job_wp(t, j_nj) = jnj
type_job_wp(t, j_cl) = jcl
type_job_wp(t, j_bd) = jbd
type_job_wp(t, j_dc) = jdc
type_job_wp(t, j_mi) = jmi

end sub


sub set_prop_wp

' ----- Proposition types -----

'                        Sq  Ch  Kn  Ar  Mk  Pr  Wz  TM  Su  Th  Me  Or  Ge  Ln  Sm  Nj  Cl  Bd  Dc  Mi
set_type_job_wp salvage,  8, 10, 10,  2, 10,  5,  5, 15,  5,  8,  0,  5, 15,  2,  5,  5, 15,  0,  0, 10
set_type_job_wp mining,  8, 10,  5,  2, 10, 10,  5,  5,  5,  8,  0, 10, 15,  2, 10,  5,  5,  0,  0, 10
set_type_job_wp explore,  8,  5, 10, 15,  5,  5, 15,  5,  5,  5,  0, 10, 15, 10,  2,  5, 10,  0,  5,  0
set_type_job_wp combat,  8,  5, 15,  5, 15,  5,  5,  5, 10,  8,  0,  2,  0, 15, 15, 20,  5,  5,  5,  0
set_type_job_wp invest,  8,  5,  2,  5,  2,  5,  2,  2,  5, 10, 15, 10,  0,  5,  5,  5,  5, 10, 10,  0
set_type_job_wp invest2,  8,  5,  2,  5,  2, 15,  2,  8,  5,  5, 15,  5, 10,  5,  5,  5, 10,  5,  5,  0
set_type_job_wp oddjob,  8, 15,  2,  5,  2,  5,  5,  5,  5, 15, 15,  2,  0,  2,  2,  5,  5, 10, 10, 10
set_type_job_wp contest,  8,  5, 15,  5, 15,  5,  5, 10,  5,  5,  5,  5,  0, 15, 15, 15,  5,  5,  0, 10

' ----- Preferred stats with job and stat bonuses -----

s = neutral
stat_job_wp(s, j_sq) = 10: stat_br_wp(s, 1)  = 10
stat_job_wp(s, j_ch) =  5: stat_br_wp(s, 2)  = 10
stat_job_wp(s, j_kn) =  5: stat_br_wp(s, 3)  = 10
stat_job_wp(s, j_ar) = 10: stat_br_wp(s, 4)  = 10
stat_job_wp(s, j_mk) =  5: stat_br_wp(s, 5)  = 10
stat_job_wp(s, j_pr) =  5:    stat_fa_wp(s, 1)  = 10
stat_job_wp(s, j_wz) =  5:    stat_fa_wp(s, 2)  = 10
stat_job_wp(s, j_tm) =  5:    stat_fa_wp(s, 3)  = 10
stat_job_wp(s, j_su) =  5:    stat_fa_wp(s, 4)  = 10
stat_job_wp(s, j_th) = 10:    stat_fa_wp(s, 5)  = 10
stat_job_wp(s, j_me) = 10:      stat_lv_wp(s,  1) =  5
stat_job_wp(s, j_or) =  5:      stat_lv_wp(s,  2) =  5
stat_job_wp(s, j_ge) =  5:      stat_lv_wp(s,  3) =  8
stat_job_wp(s, j_ln) =  5:      stat_lv_wp(s,  4) = 10
stat_job_wp(s, j_sm) =  0:      stat_lv_wp(s,  5) = 15
stat_job_wp(s, j_nj) =  5:      stat_lv_wp(s,  6) = 20
stat_job_wp(s, j_cl) =  5:      stat_lv_wp(s,  7) = 10
stat_job_wp(s, j_bd) = 10:      stat_lv_wp(s,  8) = 10
stat_job_wp(s, j_dc) = 10:      stat_lv_wp(s,  9) = 10
stat_job_wp(s, j_mi) = 20:      stat_lv_wp(s, 10) = 10

s = brave
stat_job_wp(s, j_sq) = 10: stat_br_wp(s, 1)  =  5
stat_job_wp(s, j_ch) =  0: stat_br_wp(s, 2)  =  8
stat_job_wp(s, j_kn) = 10: stat_br_wp(s, 3)  = 10
stat_job_wp(s, j_ar) =  5: stat_br_wp(s, 4)  = 15
stat_job_wp(s, j_mk) = 10: stat_br_wp(s, 5)  = 20
stat_job_wp(s, j_pr) =  0:    stat_fa_wp(s, 1)  = 20
stat_job_wp(s, j_wz) =  0:    stat_fa_wp(s, 2)  = 15
stat_job_wp(s, j_tm) =  0:    stat_fa_wp(s, 3)  = 10
stat_job_wp(s, j_su) =  0:    stat_fa_wp(s, 4)  =  8
stat_job_wp(s, j_th) =  5:    stat_fa_wp(s, 5)  =  5
stat_job_wp(s, j_me) =  0:      stat_lv_wp(s,  1) =  5
stat_job_wp(s, j_or) =  0:      stat_lv_wp(s,  2) =  8
stat_job_wp(s, j_ge) =  0:      stat_lv_wp(s,  3) = 10
stat_job_wp(s, j_ln) = 10:      stat_lv_wp(s,  4) = 15
stat_job_wp(s, j_sm) =  5:      stat_lv_wp(s,  5) = 20
stat_job_wp(s, j_nj) = 10:      stat_lv_wp(s,  6) = 20
stat_job_wp(s, j_cl) =  0:      stat_lv_wp(s,  7) = 15
stat_job_wp(s, j_bd) =  5:      stat_lv_wp(s,  8) = 10
stat_job_wp(s, j_dc) =  5:      stat_lv_wp(s,  9) =  8
stat_job_wp(s, j_mi) =  0:      stat_lv_wp(s, 10) =  5

s = faith
stat_job_wp(s, j_sq) = 10: stat_br_wp(s, 1)  = 20
stat_job_wp(s, j_ch) = 10: stat_br_wp(s, 2)  = 15
stat_job_wp(s, j_kn) =  0: stat_br_wp(s, 3)  = 10
stat_job_wp(s, j_ar) =  0: stat_br_wp(s, 4)  =  8
stat_job_wp(s, j_mk) =  0: stat_br_wp(s, 5)  =  5
stat_job_wp(s, j_pr) = 10:    stat_fa_wp(s, 1)  =  5
stat_job_wp(s, j_wz) = 10:    stat_fa_wp(s, 2)  =  8
stat_job_wp(s, j_tm) = 10:    stat_fa_wp(s, 3)  = 10
stat_job_wp(s, j_su) = 10:    stat_fa_wp(s, 4)  = 15
stat_job_wp(s, j_th) =  0:    stat_fa_wp(s, 5)  = 20
stat_job_wp(s, j_me) =  5:      stat_lv_wp(s,  1) = 10
stat_job_wp(s, j_or) = 10:      stat_lv_wp(s,  2) = 10
stat_job_wp(s, j_ge) = 10:      stat_lv_wp(s,  3) = 10
stat_job_wp(s, j_ln) =  0:      stat_lv_wp(s,  4) = 10
stat_job_wp(s, j_sm) = 10:      stat_lv_wp(s,  5) = 10
stat_job_wp(s, j_nj) =  5:      stat_lv_wp(s,  6) =  0
stat_job_wp(s, j_cl) = 10:      stat_lv_wp(s,  7) = 20
stat_job_wp(s, j_bd) =  5:      stat_lv_wp(s,  8) = 15
stat_job_wp(s, j_dc) =  5:      stat_lv_wp(s,  9) = 10
stat_job_wp(s, j_mi) =  0:      stat_lv_wp(s, 10) =  8

end sub


sub set_jn(j, nx$, np$)
job_name$(psx, j) = nx$
job_name$(psp, j) = np$
end sub


sub add_prop(nx$, np$, chapter, ptype, pstat, preward)

prop_i = prop_i + 1
prop_name$(psx, prop_i) = nx$
prop_name$(psp, prop_i) = np$
prop_chapter(prop_i)    = chapter
prop_type(prop_i)      = ptype
prop_stat(prop_i)      = pstat
prop_reward(prop_i)    = preward
prop_city(prop_i)      = prop_c

end sub


sub set_prop_data

prop_i = 0

prop_c = igros
add_prop "Girl at Gulg Volcano",      "Mount Gulg Mother Lode",        ch2,    mining,  brave,  item
add_prop "Sad Traveling Artist",      "Minstrel in Distress",          ch3,    oddjob,  faith,  0
add_prop "Traveling Artist Mameko",  "Mameco the Minstrel",          ch3,    oddjob,  neutral, 0
add_prop "Ringing of the Bell",      "Guard Duty",                    ch3b,  oddjob,  faith,  item
add_prop "Legendary Monster",        "Hellspawned Beast",            ch4,    combat,  faith,  0
add_prop "Sullen Experiment",        "Metamorphosed Misery",          ch4,    combat,  neutral, 0
add_prop "Thief Zero Reborn!",        "Zerro Strikes Again",          ch4,    combat,  brave,  item
add_prop "Legendary Traces",          "Ancient Wonder",                ch4b,  explore, neutral, land

prop_c = gariland
add_prop "Orders of the Coast Guard", "Shoreline Defense",            ch2,    invest2, neutral, 0
add_prop "Testimony of an Ex-miner",  "Miner's Tale",                  ch2,    mining,  neutral, 0
add_prop "Stolen Ancient Writings",  "Stolen Tomes",                  ch3,    salvage, neutral, 0
add_prop "Master Math!",              "Arithmeticks Tutor Wanted",    ch4,    oddjob,  faith,  0
add_prop "Win the Magic Contest!",    "Magick Melee",                  virgo,  contest, faith,  item

prop_c = dorter
add_prop "Trap of the Bandits",      "Bandits",                      ch2,    combat,  faith,  0
add_prop "Discovery Race",            "Frontier Marathon",            ch3,    explore, neutral, land
add_prop "Discovery Race 2",          "Second Frontier Marathon",      ch3,    explore, neutral, land
add_prop "Discovery Race 3",          "Third Frontier Marathon",      ch3,    explore, neutral, land
add_prop "Minimum's Melancholy",      "Count Minimas",                ch4,    oddjob,  brave,  item
add_prop "Minimum's Melancholy",      "Count Minimas",                ch4,    oddjob,  brave,  item
add_prop "Minimum's Melancholy",      "Count Minimas",                ch4,    combat,  neutral, 0

prop_c = zaland
add_prop "Salvage the Trade Ship!",  "The Hindenburg",                ch2,    salvage, faith,  0
add_prop "Zaland Embassy",            "Zaland Embassy Antiques",      ch2,    salvage, neutral, item
add_prop "Rolade Ore Company",        "Lorraide Mine",                ch3,    mining,  brave,  0
add_prop "Deep in Sweegy Woods",      "The Siedge Weald",              ch3b,  explore, neutral, land
add_prop "Shy Katedona",              "Cattedona",                    ch4b,  oddjob,  faith,  0
add_prop "Win the Zaland Fight!",    "The Zaland Melee",              aries,  contest, brave,  0

prop_c = lionel
add_prop "My Little Carrot",          "My Little Carrot",              ch2,    invest,  neutral, 0
add_prop "Trade Ship Douing",        "The Dawn Queen",                ch3,    salvage, neutral, 0
add_prop "Challenge of Zero",        "Zerro's Challenge",            ch3,    combat,  brave,  item
add_prop "I saw it.",                "The Trick of Light",            ch3b,  explore, neutral, land
add_prop "Storm of Zigolis!",        "Fenland Mystery",              ch3b,  explore, faith,  land
add_prop "Protect the Little Life",  "Father's Nightmare",            ch4,    invest,  neutral, item
add_prop "Emissary of Lionel",        "Lionel Emissary",              ch4b,  salvage, neutral, item

prop_c = goug
add_prop "Vacancy!",                  "Miner Shortage",                ch2,    mining,  neutral, 0
add_prop "Heir of Mesa",              "Masa's Legacy",                ch3,    salvage, neutral, 0
add_prop "Machinist Contest",        "Clockwork Faire",              ch3,    oddjob,  neutral, item
add_prop "Salvage the Trade Ship!",  "The Durga",                    ch4,    salvage, brave,  item
add_prop "Devil in the Dark",        "Devil in the Dark",            ch4,    invest2, neutral, 0
add_prop "Meister Contest",          "Arteficer's Contest",          sagit,  contest, neutral, 0

prop_c = warjilis
add_prop "Destiny of the Company",    "The Highwind",                  ch2,    salvage, faith,  0
add_prop "Concerns of a Merchant",    "Merchant's Regret",            ch3,    explore, neutral, land
add_prop "Mountain of Rain",          "Rain-Swept Slopes",            ch4,    explore, brave,  land
add_prop "Within the Darkness",      "In the Darkness",              ch4,    combat,  faith,  0
add_prop "True Romance",              "True Romance",                  ch4,    oddjob,  neutral, item
add_prop "Wandering Gambler",        "Wandering Gambler",            ch4,    oddjob,  neutral, 0

prop_c = goland
add_prop "Will of Elder Topa",        "Old Tappa's Will",              ch3,    mining,  brave,  0
add_prop "Miners Wanted!",            "Coal Miners Wanted",            ch4,    mining,  brave,  0
add_prop "Miners Wanted! 2",          "More Coal Miners Wanted",      ch4,    mining,  brave,  item
add_prop "Adventurer Ramzen",        "Lamzen the Adventurer",        ch4,    explore, neutral, land
add_prop "Defeat Golden Gotsko!",    "Twilight Gustkov",              ch4b,  combat,  neutral, 0
add_prop "Terror of Assault Cave",    "Terror's Maw",                  ch4b,  combat,  faith,  0
add_prop "Dream of a Miner",          "Miner's Dream",                ch4b,  mining,  neutral, 0

prop_c = lesalia
add_prop "Sunken Salvage Tour",      "Salvage Expedition",            ch3,    salvage, neutral, item
add_prop "Mine Excavation Tour",      "Abandoned Mine",                ch3,    mining,  neutral, item
add_prop "Discovery Tour",            "Frontier Expedition",          ch4,    explore, neutral, land
add_prop "Thief Zero Returns!",      "Zerro's Return",                ch4,    combat,  brave,  item
add_prop "If wishes come true",      "Ducal Disaster",                ch4b,  invest,  neutral, 0
add_prop "Son, Pappal!",              "Young Lord Pappal",            ch4b,  combat,  brave,  0
add_prop "Secret Door",              "Cries in the Dark",            ch4b,  invest2, neutral, 0

prop_c = yardow
add_prop "Sailor Tour",              "Diving Expedition",            ch3b,  salvage, brave,  0
add_prop "Envoy ship, Falcon",        "The Falcon",                    ch4,    salvage, brave,  item
add_prop "Good Workplace and Job!",  "Salvage Work",                  ch4b,  salvage, neutral, 0
add_prop "Miner's Tour",              "Coal Mining Expedition",        ch4b,  mining,  neutral, 0
add_prop "Miner's Tour 2",            "Second Coal Mining Expedition", ch4b,  mining,  neutral, item
add_prop "Win the Yardow Fight!",    "The Yardrow Melee",            cancer, contest, brave,  item

prop_c = bervenia
add_prop "Hidden Trap at the Maze",  "Endless Caverns",              ch4,    mining,  neutral, item
add_prop "One Activity",              "Past Glory",                    ch4,    mining,  brave,  item
add_prop "Ruins at Bed Desert",      "Beddha Sandwaste",              ch4,    explore, neutral, land
add_prop "Adventurer Wanted!",        "Adventurers Wanted",            ch4,    explore, neutral, land
add_prop "I saw it! I swear!",        "Shadows from the Past",        ch4,    explore, neutral, land
add_prop "Defeat Behemoth!",          "The Behemoth",                  ch4b,  combat,  brave,  0

prop_c = riovanes
add_prop "Sea of Gredia Island",      "Gleddia Isle",                  ch4,    salvage, faith,  item
add_prop "Stranded Trade Ship",      "Foundered Vessel",              ch4,    salvage, faith,  0
add_prop "Fiar's Request",            "Fia's Wish",                    ch4,    combat,  neutral, 0
add_prop "Secret Society",            "Secret Society",                ch4,    invest2, brave,  0
add_prop "Letter to My Love",        "Lettre d'amour",                ch4,    oddjob,  neutral, item
add_prop "The Greatest Plan",        "Historic Revolt",              ch4b,  invest2, neutral, item
add_prop "Hard Lecture",              "Tutoring",                      ch4b,  oddjob,  faith,  0

prop_c = zeltennia
add_prop "Larner Channel Waves",      "Rhana Straight",                ch4,    salvage, faith,  item
add_prop "Mother",                    "Nightwalker",                  ch4,    invest2, neutral, 0
add_prop "Phantom Thief Zero!",      "Zerro Strikes",                ch4,    combat,  brave,  item
add_prop "Attractive Workplace",      "Dredge Work",                  ch4b,  salvage, brave,  0
add_prop "Dream child",              "Missing Boy",                  ch4b,  invest,  neutral, 0
add_prop "How much is Life worth?",  "Appraisal",                    ch4b,  invest2, neutral, item

prop_c = zarghidas
add_prop "Himka Cliffs",              "Himca Cliffs",                  ch4,    mining,  neutral, item
add_prop "The Lord's Ore",            "Ore of the Gods",              ch4,    mining,  neutral, item
add_prop "Death Canyon",              "Death's Gorge",                ch4b,  mining,  neutral, 0
add_prop "Defeat Whirlwind Karz!",    "The Typhoon",                  ch4b,  combat,  brave,  0
add_prop "Road of Beasts",            "Beastly Trail",                ch4b,  oddjob,  faith,  0
add_prop "Memories",                  "Memories",                      ch4b,  oddjob,  neutral, 0

prop_c = limberry
add_prop "Poeskas Lake Bottom",      "Lake Poescas Depths",          ch4b,  explore, brave,  land
add_prop "Ominous Dungeon",          "Cellar Dungeon",                ch4b,  explore, neutral, land
add_prop "My treasure",              "Uninvited Guests",              ch4b,  invest2, neutral, 0
add_prop "Chocobo Restaurant",        "Gysahl Greens",                ch4b,  oddjob,  brave,  item
add_prop "Wandering Gambler",        "Wandering Gambler",            ch4b,  oddjob,  neutral, 0
add_prop "Thief Zero's Last Stand",  "Zerro's Final Heist",          ch4b,  combat,  brave,  0

end sub


' Input handling routine library - keyboard, gamepad, mouse

' NOTE: Mouse not yet implemented.

' NOTE: Keybinding menu must be made separately.

' No dependencies



function new_press(b)
new_press = false
if press(b) = true and hold(b) = false then new_press = true
end function


sub update_inputs

call detect_devices

for b = 1 to keybind_count
  hold(b) = press(b)
  press(b) = false

  d = keyboard
  if dev_keyboard <> false then
      z = deviceinput(dev_keyboard)
      if button(keybind(b, d)) = true then press(b) = true
  end if

  d = gamepad
  if dev_gamepad <> false and keybind(b, d) <> false then
      z = deviceinput(dev_gamepad)

      if keybind(b, d) < 100 then ' Button
        if button(keybind(b, d)) = true then press(b) = true

      ' Stick handling:
      ' keybind() set to 101, 102 etc. is an assignment of stick 1, 2 etc. in the negative direction
      ' keybind() set to 201, 202 etc. is an assignment of stick 1, 2 etc. in the positive direction
      elseif keybind(b, d) > 200 then ' Stick positive
        if axis(keybind(b, d) - 200) >  option_sensitivity * 0.1 then press(b) = true
      else                            ' Stick negative
        if axis(keybind(b, d) - 100) < -option_sensitivity * 0.1 then press(b) = true
      end if
  end if
next b

if dev_mouse = false then exit sub

dim mb(3)
for b = 1 to 3
  mb(b) = mouse_press(b)
next b

mouse_change = false
do while mouseinput <> false and mouse_change = false
  mouse_pos_x = mousex
  mouse_pos_y = mousey
  for b = 1 to 3
      mb(b) = mousebutton(b)
      if mb(b) <> mouse_press(b) then mouse_change = true
  next b
loop

for b = 1 to 3
  mouse_hold(b) = mouse_press(b)
  mouse_press(b) = mb(b)

  if mouse_press(b) = true and mouse_hold(b) = false then
      mouse_press_x(b) = mouse_pos_x ' Store position mouse button was pressed
      mouse_press_y(b) = mouse_pos_y
  elseif mouse_press(b) = false and mouse_hold(b) = true then
      mouse_release_x(b) = mouse_pos_x ' Store position mouse button was released
      mouse_release_y(b) = mouse_pos_y
  end if
next b

end sub


sub detect_devices

dev_keyboard = false
dev_gamepad  = false
dev_mouse    = false

d = devices
for n = d to 1 step -1
  if left$(device$(n), 10) = "[KEYBOARD]"  then dev_keyboard = n
  if left$(device$(n), 12) = "[CONTROLLER]" then dev_gamepad  = n
  if left$(device$(n),  7) = "[MOUSE]"      then dev_mouse    = n
next n

end sub


sub set_press(p)
for b = 1 to keybind_count
  press(b) = p
next b
end sub


sub set_hold(p)
for b = 1 to keybind_count
  hold(b) = p
next b
end sub


sub press_any_key
do
  limit 60
  display
  for b = 1 to keybind_count
      if new_press(b) = true then exit sub
  next b
  call update_inputs
loop
end sub


sub set_default_keybinds

call detect_devices

for b = 1 to keybind_count
  keybind(b, keyboard) = keybind_default(b, keyboard)
  keybind(b, gamepad)  = keybind_default(b, gamepad)
next b

if dev_gamepad = false then exit sub

' Eliminate any defaults that go beyond a gamepad's features
d = gamepad

for b = 1 to keybind_count
  if keybind(b, d) < 100 and keybind(b, d) > lastbutton(dev_gamepad) then keybind(b, d) = false
next b

if lastaxis(dev_gamepad) < 2 then
  keybind(up_key,    d) = false
  keybind(down_key,  d) = false
  keybind(left_key,  d) = false
  keybind(right_key, d) = false
end if

end sub


sub set_key_data

key_name$(false, keyboard) = "NOT SET"
key_name$(false, gamepad)  = "NOT SET"

d = keyboard
for n = 1 to 512
  key_name$(n, d) = "UNKNOWN"
next n
key_name$(2,  d) = "ESC"
key_name$(60,  d) = "F1"
key_name$(61,  d) = "F2"
key_name$(62,  d) = "F3"
key_name$(63,  d) = "F4"
key_name$(64,  d) = "F5"
key_name$(65,  d) = "F6"
key_name$(66,  d) = "F7"
key_name$(67,  d) = "F8"
key_name$(68,  d) = "F9"
key_name$(88,  d) = "F11"
key_name$(89,  d) = "F12"
key_name$(42,  d) = "~"
key_name$(3,  d) = "1"
key_name$(4,  d) = "2"
key_name$(5,  d) = "3"
key_name$(6,  d) = "4"
key_name$(7,  d) = "5"
key_name$(8,  d) = "6"
key_name$(9,  d) = "7"
key_name$(10,  d) = "8"
key_name$(11,  d) = "9"
key_name$(12,  d) = "0"
key_name$(13,  d) = "-"
key_name$(14,  d) = "="
key_name$(15,  d) = "BKSP"
key_name$(16,  d) = "TAB"
key_name$(17,  d) = "Q"
key_name$(18,  d) = "W"
key_name$(19,  d) = "E"
key_name$(20,  d) = "R"
key_name$(21,  d) = "T"
key_name$(22,  d) = "Y"
key_name$(23,  d) = "U"
key_name$(24,  d) = "I"
key_name$(25,  d) = "O"
key_name$(26,  d) = "P"
key_name$(27,  d) = "["
key_name$(28,  d) = "]"
key_name$(44,  d) = "\"
key_name$(31,  d) = "A"
key_name$(32,  d) = "S"
key_name$(33,  d) = "D"
key_name$(34,  d) = "F"
key_name$(35,  d) = "G"
key_name$(36,  d) = "H"
key_name$(37,  d) = "J"
key_name$(38,  d) = "K"
key_name$(39,  d) = "L"
key_name$(40,  d) = ";"
key_name$(41,  d) = "'"
key_name$(29,  d) = "ENTER"
key_name$(43,  d) = "L SHIFT"
key_name$(45,  d) = "Z"
key_name$(46,  d) = "X"
key_name$(47,  d) = "C"
key_name$(48,  d) = "V"
key_name$(49,  d) = "B"
key_name$(50,  d) = "N"
key_name$(51,  d) = "M"
key_name$(52,  d) = ","
key_name$(53,  d) = "."
key_name$(54,  d) = "/"
key_name$(55,  d) = "R SHIFT"
key_name$(30,  d) = "L CTRL"
key_name$(58,  d) = "SPACE"
key_name$(286, d) = "R CTRL"
key_name$(339, d) = "INS"
key_name$(340, d) = "DEL"
key_name$(328, d) = "HOME"
key_name$(336, d) = "END"
key_name$(330, d) = "PG UP"
key_name$(338, d) = "PG DN"
key_name$(329, d) = "UP"
key_name$(337, d) = "DOWN"
key_name$(332, d) = "LEFT"
key_name$(334, d) = "RIGHT"
key_name$(310, d) = "NUM /"
key_name$(56,  d) = "NUM *"
key_name$(75,  d) = "NUM -"
key_name$(79,  d) = "NUM +"
key_name$(285, d) = "NUM ENTER"
key_name$(72,  d) = "NUM 7"
key_name$(73,  d) = "NUM 8"
key_name$(74,  d) = "NUM 9"
key_name$(76,  d) = "NUM 4"
key_name$(77,  d) = "NUM 5"
key_name$(78,  d) = "NUM 6"
key_name$(80,  d) = "NUM 1"
key_name$(81,  d) = "NUM 2"
key_name$(82,  d) = "NUM 3"
key_name$(83,  d) = "NUM 0"
key_name$(84,  d) = "NUM ."

' Troublesome keyboard codes:
'  71 - Scroll Lock
'  70 - Pause
'  59 - Caps Lock
'  348 - Windows Left
'  349 - Windows Right?
'  350 - Menu
'  326 - Num Lock

d = gamepad
for n = 1 to 20
  key_name$(n, d) = "BUTTON" + str$(n)
next n
for n = 1 to 8
  key_name$(n + 100, d) = "AXIS" + str$(n) + "-" ' *** Check that first axis is horizontal
  key_name$(n + 200, d) = "AXIS" + str$(n) + "+"
next n

end sub


sub glass_fonts(t1$, x1, y1)
' Text, font, destination image surface, position, alignment

t$ = t1$
carriage = true
if right$(t$, 1) = ";" then
  carriage = false
  t$ = left$(t$, len(t$) - 1)
end if

x = x1: y = y1
f = font_using

if font_align <> left_align then
  ' Adjust starting point based on line width, for center or right align
  w = text_width(t$, f)
  if font_align = center_align then w = int(w * 0.5)
  x = x - w
end if

for n = 1 to len(t$)
  c = asc(mid$(t$, n, 1))
  w = g_font(f, c).w
  putimage(x, y)-step(w, g_font(f, 0).h), g_font(f, 0).image, font_dest, (g_font(f, c).pos.x, g_font(f, c).pos.y)-step(w, g_font(f, 0).h)
  x = x + w + 1
next n

font_x = x1
font_y = y1
if carriage = false then font_x = x
if carriage = true  then font_y = y1 + g_font(f, 0).h

end sub


sub glass_fonts_at(t$)
glass_fonts t$, font_x, font_y
end sub


sub set_font(f, a, d&)

font_using = f
font_align = a
font_dest  = d&

end sub


sub font_pos(x, y)

font_x = x
font_y = y

end sub


sub initialize_font(f)

preserve& = source

source g_font(f, 0).image
clearcolor point(0, 0), g_font(f, 0).image
i& = g_font(f, 0).image
d~& = point(1, 0) ' Detection color

' Height
g_font(f, 0).h = scan_down(1, 2, i&, d~&) - 3

y = 0
for cy = 0 to 15
  y = scan_down(1, y, i&, d~&) + 1
  x = 1
  for cx = 0 to 15
      n = (cy * 16) + cx
      g_font(f, n).pos.x = x ' Source position
      g_font(f, n).pos.y = y
      x = scan_right(x, y, i&, d~&) + 1
      g_font(f, n).w = x - g_font(f, n).pos.x - 2 ' Variable width
  next cx
next cy

source preserve&

end sub


function font_height
font_height = g_font(font_using, 0).h
end function


function text_width(t$, f)
w = 0
for n = 1 to len(t$)
  w = w + g_font(f, asc(mid$(t$, n, 1))).w + 1
next n
text_width = w - 1
end function


function scan_right(x1, y, i&, d~&) ' Starting position (noninclusive), image, detection color
x = x1
preserve& = source
source i&
w = width(i&)
do
  x = x + 1
  if x > w then call scan_error(x, y, "right")
loop until point(x, y) = d~& or x > w
scan_right = x
source preserve&
end function


function scan_down(x, y1, i&, d~&)
y = y1
preserve& = source
source i&
h = height(i&)
do
  y = y + 1
  if y > h then call scan_error(x, y, "down")
loop until point(x, y) = d~& or y > h
scan_down = y
source preserve&
end function


sub scan_error(x, y, t$)
t1$ = "Moved " + t$ + " beyond image at" + str$(x) + "," + str$(y)
set_font f_kharon, left_align, full_screen
glass_fonts t1$, 0, 0
display: sleep
end sub


' Math and logic routines

' No data structure or dependencies


function plus_limit(n, p, l) ' p is added to n, but can't go past l in the direction of travel
q = n + p
if sgn(q - l) = sgn(p) then q = l
plus_limit = q
end function


function half(n) ' less expensive than n / 2, less parentheses than n * 0.5
half = n * 0.5
end function
function inthalf(n) ' same, but with int() around it
inthalf = int(n * 0.5)
end function


function sq(n) ' less expensive and less parentheses than n ^ 2
' For code clarity
sq = n * n
end function


function atn1(n) ' shortcut for getting radians of eight cardinals and diagonals
' For code clarity - n represents multiple of 45 degrees, or quarter-pi radians
atn1 = n * atn(1)
end function


function degrees(d) ' pass in degrees, returns radians
degrees = atn1(d / 45)
end function


function hypo(a, b) ' pass in triangle legs, returns hypotenuse (Pythagoras)
hypo = sqr(sq(a) + sq(b)) ' squares are always positive, so no danger of imaginary component
end function


function arctan(y, x) ' atn() with safety checks, and sensitive to negative axes
arctan = 0
if x = 0 and y = 0 then exit function
a = atn1(2)
if x <> 0 then ' prevent division by zero
  a = abs(atn(y / x))
  if x < 0 then a = atn1(4) - a
end if
if y < 0 then a = flip_y(a)
arctan = a
end function


function flip_x(a) ' flips angle left/right
flip_x = wrap_a( (atn1(8) - wrap_a(a + atn1(2))) - atn1(2) )
end function
function flip_y(a) ' flips angle up/down
flip_y = atn1(8) - a
end function


function frames(s) ' pass a decimal as seconds.frames, returns integer frames
f = int(s) * 60
frames = int(f + ((s - int(s)) * 100))
end function


function frames_dec(s) ' like frames(), but takes seconds.decimal instead of seconds.frames,
f = int(s) * 60        ' so 0.50 will return 30 frames, not 50 frames
frames_dec = int(f + ((s - int(s)) * 60))
end function


function wrap(n, l1, h1) ' n is adjusted back within lower(l) and upper(h) bounds similar to mod operator
l = l1: h = h1 ' make sure h is never less than l, this also prevents division by zero
if h1 < l1 then
  l = h1: h = l1
end if
x = (l - n) / ((h - l) + 1)
if x <> int(x) then x = x + 1
wrap = n + (int(x) * ((h - l) + 1))
end function


function wrap_a(a) ' angle a is adjusted back within 0 and 2pi, noninclusive of 2pi
x = -a / atn1(8)
if x <> int(x) then x = x + 1
wrap_a = a + (int(x) * atn1(8))
end function


function toggle(v, p, q)
if v = p then toggle = q
if v = q then toggle = p
end function


function rounding(n) ' rounds to closer integer
p = int(n)
if mod_dec(n, 1) => 0.5 then p = p + 1
rounding = p
end function


function min(n1, n2)
if n2 < n1 then min = n2 else min = n1
end function


function max(n1, n2)
if n2 > n1 then max = n2 else max = n1
end function


function pyr(n) ' produce pyramid number on n (1 + 2 + 3 ... n)
pyr = n * (n + 1) * 0.5
end function


function rand(n) ' produce random whole number from 1 to n
rand = int(rnd * n) + 1
end function


function mod_dec(n, d) ' mod operator that preserves decimal
mod_dec = n
if d = 0 then exit function ' Division by zero protection
mod_dec = ((n / d) - int(n / d)) * d
end function


function hexcolor~&(h$)
hexcolor~& = rgba32(0, 0, 0, 255)
if len(h$) <> 6 then exit function
hexcolor~& = rgba32(val("&H" + mid$(h$, 1, 2)), val("&H" + mid$(h$, 3, 2)), val("&H" + mid$(h$, 5, 2)), 255)
end function


function before$(t$, c$)
p = instr(t$, c$)
if p = false then p = len(t$) + 1
before$ = left$(t$, p - 1)
end function


function after$(t$, c$)
after$ = right$(t$, len(t$) - instr(t$, c$) - (len(c$) - 1))
end function


function between$(t$, c1$, c2$)
between$ = before$(after$(t$, c1$), c2$)
end function


function vector_x(a, v) ' convert polar vector to x component
vector_x = 0
if a = aim_n or a = aim_s then exit function ' Protect against undefined cos()
vector_x = v * cos(a)
end function
function vector_y(a, v) ' convert polar vector to y component
vector_y = 0
if a = aim_w or a = aim_e then exit function ' Protect against undefined sin()
vector_y = v * sin(a)
end function


function ellipse_focus_x(axis_x, axis_y)
ellipse_focus_x = 0
if axis_x > axis_y then ellipse_focus_x = sqr(sq(axis_x) - sq(axis_y))
end function
function ellipse_focus_y(axis_x, axis_y)
ellipse_focus_y = 0
if axis_x < axis_y then ellipse_focus_y = sqr(sq(axis_y) - sq(axis_x))
end function


function x_on_ellipse(ax, ay, angle)

select case angle
  case atn1(0): ex = ax
  case atn1(4): ex = ax
  case atn1(2): ex = 0
  case atn1(6): ex = 0
  case else: ex = (ax * ay) / sqr(sq(ay) + sq(ax * tan(angle)))
end select

if angle > atn1(2) and angle < atn1(6) then ex = -ex
x_on_ellipse = ex

end function


function y_on_ellipse(ax, ay, angle)

select case angle
  case atn1(0): ey = 0
  case atn1(4): ey = 0
  case atn1(2): ey = ay
  case atn1(6): ey = ay
  case else: ey = (ax * ay) / sqr(sq(ax) + sq(ay / tan(angle)))
end select

if angle > atn1(4) then ey = -ey
y_on_ellipse = ey

end function


function ellipse_tangent(ax, ay, angle)

' ax and ay are axis lengths from center of ellipse
' angle is from center of ellipse
' Returns tangent angle, facing in clockwise direction

' Point angle intersects ellipse
ix = x_on_ellipse(ax, ay, angle)
iy = y_on_ellipse(ax, ay, angle)

' Focus distance from center
fx = ellipse_focus_x(ax, ay)
fy = ellipse_focus_y(ax, ay)

' Angles from foci to intersection point
a1 = arctan(iy + fy, ix + fx)
a2 = arctan(iy - fy, ix - fx)

' Average, then right angle to get tangent angle
ellipse_tangent = wrap_a(half(a1 + a2) + atn1(2))

end function


function line_and_ellipse(x1, y1, x2, y2, axis_x, axis_y, ix, iy)

' Given a line between points (x1, y1) and (x2, y2) relative to an ellipse's center,
' find the x coordinate of the intersection between the line and the ellipse,
' closer to (x1, y1), and put output in (ix, iy).

ix = 0 ' Default to center of ellipse
iy = 0
line_and_ellipse = true ' Becomes false later if the quadratic's radical is negative

if axis_x = 0 and axis_y = 0 then exit function

fx = ellipse_focus_x(axis_x, axis_y)
fy = ellipse_focus_y(axis_x, axis_y)

dx = sgn(x2 - x1)
dy = sgn(y2 - y1)

' Handle pure vertical and horizontal
if dx = 0 or dy = 0 then
  ix = x1
  iy = y1
  if dx = 0 and dy <> 0 and axis_x > 0 then
      iy = -dy * sqr((sq(axis_y) * abs(sq(axis_x) - sq(x1))) / sq(axis_x))
  end if
  if dy = 0 and dx <> 0 and axis_y > 0 then
      ix = -dx * sqr((sq(axis_x) * abs(sq(axis_y) - sq(y1))) / sq(axis_y))
  end if

' Otherwise, run quadratic solution of line and ellipse
else
  slope = (y2 - y1) / (x2 - x1)
  elevation = y1 - (slope * x1)

  ' Quadratic coefficients
  a = sq(axis_x * slope) + sq(axis_y)
  b = 2 * sq(axis_x) * slope * elevation
  c = sq(axis_x) * (sq(elevation) - sq(axis_y))

  if sq(b) - (4 * a * c) < 0 then
      line_and_ellipse = false ' Negative will fail the quadratic radical,
      exit function            ' calling routine must be alerted
  end if

  ' Use x coordinate closer to (x1, y1)
  ix1 = quadratic(a, b, c,  1)
  ix2 = quadratic(a, b, c, -1)
  if abs(x1 - ix1) < abs(x1 - ix2) then ix = ix1 else ix = ix2

  iy = (slope * ix) + elevation
end if

end function


function quadratic(a, b, c, pm)
' pm is 1 or -1, to represent the +/- in the quadratic formula
if a = 0 then
  print "Quadratic denominator was zero!"
  display: sleep
  exit function
end if
quadratic = (-b + (pm * sqr(sq(b) - (4 * a * c)))) / (2 * a)
end function



RE: "Girl at Gulg Volcano" - Final Fantasy Tactics Proposition Calculator - Pete - 03-08-2024

So when you finished coding it, what did you do with the rest of your day? Huh 

No, that's impressive. I looked through the code a couple of times, it certainly is a lot of work in several aspects of planning, creation and organizing.

+2

Pete Smile


RE: "Girl at Gulg Volcano" - Final Fantasy Tactics Proposition Calculator - johannhowitzer - 03-09-2024

Heh, the practical side of this program, the calculating of mission success, that has existed for a couple years I think.  The UI was ASCII until I decided to overhaul it and see just how close I could get to the game's own graphics and menu movement feel.

Most of the time was spent trying to get accurate, complete captures of the game's assets... right down to the drop shadows, which Final Fantasy Tactics does not blend with current alpha method.  It took some testing to reveal that it just does a straight subtraction on the RGB values.  So the blending had to be done by hand. It intentionally does not have any sounds, because the idea would be to run this program while playing the game, and then you'd have two sets of sounds going on at the same time.