From 1dfdcc7252ac83643cae7a7447c025da2af63843 Mon Sep 17 00:00:00 2001 From: athrxx Date: Sat, 26 Jan 2019 01:31:34 +0100 Subject: KYRA: cleanup dir Reorganize all files in sub directories. The file placement isn't as intuitive as it might be for other engines, which is probably the reason why this hasn't been done before. --- engines/kyra/animator_hof.cpp | 313 -- engines/kyra/animator_lok.cpp | 651 ---- engines/kyra/animator_lok.h | 127 - engines/kyra/animator_mr.cpp | 456 --- engines/kyra/animator_tim.cpp | 235 -- engines/kyra/animator_v2.cpp | 187 -- engines/kyra/chargen.cpp | 1982 ----------- engines/kyra/darkmoon.cpp | 493 --- engines/kyra/darkmoon.h | 139 - engines/kyra/debugger.cpp | 744 ----- engines/kyra/debugger.h | 138 - engines/kyra/detection.cpp | 12 +- engines/kyra/engine/chargen.cpp | 1982 +++++++++++ engines/kyra/engine/darkmoon.cpp | 493 +++ engines/kyra/engine/darkmoon.h | 139 + engines/kyra/engine/eob.cpp | 573 ++++ engines/kyra/engine/eob.h | 125 + engines/kyra/engine/eobcommon.cpp | 2536 +++++++++++++++ engines/kyra/engine/eobcommon.h | 1185 +++++++ engines/kyra/engine/item.h | 41 + engines/kyra/engine/items_eob.cpp | 732 +++++ engines/kyra/engine/items_hof.cpp | 424 +++ engines/kyra/engine/items_lok.cpp | 960 ++++++ engines/kyra/engine/items_lol.cpp | 594 ++++ engines/kyra/engine/items_mr.cpp | 536 +++ engines/kyra/engine/items_v2.cpp | 100 + engines/kyra/engine/kyra_hof.cpp | 1933 +++++++++++ engines/kyra/engine/kyra_hof.h | 697 ++++ engines/kyra/engine/kyra_lok.cpp | 990 ++++++ engines/kyra/engine/kyra_lok.h | 816 +++++ engines/kyra/engine/kyra_mr.cpp | 1426 ++++++++ engines/kyra/engine/kyra_mr.h | 670 ++++ engines/kyra/engine/kyra_rpg.cpp | 366 +++ engines/kyra/engine/kyra_rpg.h | 388 +++ engines/kyra/engine/kyra_v1.cpp | 698 ++++ engines/kyra/engine/kyra_v2.cpp | 244 ++ engines/kyra/engine/kyra_v2.h | 400 +++ engines/kyra/engine/lol.cpp | 4513 ++++++++++++++++++++++++++ engines/kyra/engine/lol.h | 1345 ++++++++ engines/kyra/engine/magic_eob.cpp | 1381 ++++++++ engines/kyra/engine/scene_eob.cpp | 862 +++++ engines/kyra/engine/scene_hof.cpp | 737 +++++ engines/kyra/engine/scene_lok.cpp | 1301 ++++++++ engines/kyra/engine/scene_lol.cpp | 1577 +++++++++ engines/kyra/engine/scene_mr.cpp | 787 +++++ engines/kyra/engine/scene_rpg.cpp | 658 ++++ engines/kyra/engine/scene_v1.cpp | 360 ++ engines/kyra/engine/scene_v2.cpp | 227 ++ engines/kyra/engine/sprites.cpp | 575 ++++ engines/kyra/engine/sprites.h | 99 + engines/kyra/engine/sprites_eob.cpp | 1285 ++++++++ engines/kyra/engine/sprites_lol.cpp | 1558 +++++++++ engines/kyra/engine/sprites_rpg.cpp | 46 + engines/kyra/engine/timer.cpp | 304 ++ engines/kyra/engine/timer.h | 106 + engines/kyra/engine/timer_eob.cpp | 361 ++ engines/kyra/engine/timer_hof.cpp | 110 + engines/kyra/engine/timer_lok.cpp | 192 ++ engines/kyra/engine/timer_lol.cpp | 206 ++ engines/kyra/engine/timer_mr.cpp | 101 + engines/kyra/engine/timer_rpg.cpp | 90 + engines/kyra/engine/util.cpp | 148 + engines/kyra/engine/util.h | 48 + engines/kyra/eob.cpp | 573 ---- engines/kyra/eob.h | 125 - engines/kyra/eobcommon.cpp | 2536 --------------- engines/kyra/eobcommon.h | 1185 ------- engines/kyra/graphics/animator_hof.cpp | 313 ++ engines/kyra/graphics/animator_lok.cpp | 651 ++++ engines/kyra/graphics/animator_lok.h | 127 + engines/kyra/graphics/animator_mr.cpp | 456 +++ engines/kyra/graphics/animator_tim.cpp | 235 ++ engines/kyra/graphics/animator_v2.cpp | 187 ++ engines/kyra/graphics/screen.cpp | 3971 ++++++++++++++++++++++ engines/kyra/graphics/screen.h | 736 +++++ engines/kyra/graphics/screen_eob.cpp | 1974 +++++++++++ engines/kyra/graphics/screen_eob.h | 177 + engines/kyra/graphics/screen_hof.cpp | 96 + engines/kyra/graphics/screen_hof.h | 50 + engines/kyra/graphics/screen_lok.cpp | 447 +++ engines/kyra/graphics/screen_lok.h | 111 + engines/kyra/graphics/screen_lol.cpp | 893 +++++ engines/kyra/graphics/screen_lol.h | 105 + engines/kyra/graphics/screen_mr.cpp | 130 + engines/kyra/graphics/screen_mr.h | 53 + engines/kyra/graphics/screen_v2.cpp | 461 +++ engines/kyra/graphics/screen_v2.h | 81 + engines/kyra/graphics/vqa.cpp | 667 ++++ engines/kyra/graphics/vqa.h | 161 + engines/kyra/graphics/wsamovie.cpp | 463 +++ engines/kyra/graphics/wsamovie.h | 134 + engines/kyra/gui.cpp | 134 - engines/kyra/gui.h | 137 - engines/kyra/gui/debugger.cpp | 744 +++++ engines/kyra/gui/debugger.h | 138 + engines/kyra/gui/gui.cpp | 134 + engines/kyra/gui/gui.h | 137 + engines/kyra/gui/gui_eob.cpp | 4339 +++++++++++++++++++++++++ engines/kyra/gui/gui_eob.h | 172 + engines/kyra/gui/gui_hof.cpp | 1162 +++++++ engines/kyra/gui/gui_hof.h | 84 + engines/kyra/gui/gui_lok.cpp | 1133 +++++++ engines/kyra/gui/gui_lok.h | 184 ++ engines/kyra/gui/gui_lol.cpp | 2910 +++++++++++++++++ engines/kyra/gui/gui_lol.h | 179 + engines/kyra/gui/gui_mr.cpp | 1584 +++++++++ engines/kyra/gui/gui_mr.h | 87 + engines/kyra/gui/gui_rpg.cpp | 134 + engines/kyra/gui/gui_v1.cpp | 620 ++++ engines/kyra/gui/gui_v1.h | 197 ++ engines/kyra/gui/gui_v2.cpp | 879 +++++ engines/kyra/gui/gui_v2.h | 233 ++ engines/kyra/gui/saveload.cpp | 278 ++ engines/kyra/gui/saveload_eob.cpp | 1301 ++++++++ engines/kyra/gui/saveload_hof.cpp | 331 ++ engines/kyra/gui/saveload_lok.cpp | 319 ++ engines/kyra/gui/saveload_lol.cpp | 583 ++++ engines/kyra/gui/saveload_mr.cpp | 329 ++ engines/kyra/gui/saveload_rpg.cpp | 127 + engines/kyra/gui_eob.cpp | 4339 ------------------------- engines/kyra/gui_eob.h | 172 - engines/kyra/gui_hof.cpp | 1162 ------- engines/kyra/gui_hof.h | 84 - engines/kyra/gui_lok.cpp | 1133 ------- engines/kyra/gui_lok.h | 184 -- engines/kyra/gui_lol.cpp | 2910 ----------------- engines/kyra/gui_lol.h | 179 - engines/kyra/gui_mr.cpp | 1584 --------- engines/kyra/gui_mr.h | 87 - engines/kyra/gui_rpg.cpp | 134 - engines/kyra/gui_v1.cpp | 620 ---- engines/kyra/gui_v1.h | 197 -- engines/kyra/gui_v2.cpp | 879 ----- engines/kyra/gui_v2.h | 233 -- engines/kyra/item.h | 41 - engines/kyra/items_eob.cpp | 732 ----- engines/kyra/items_hof.cpp | 424 --- engines/kyra/items_lok.cpp | 960 ------ engines/kyra/items_lol.cpp | 594 ---- engines/kyra/items_mr.cpp | 536 --- engines/kyra/items_v2.cpp | 100 - engines/kyra/kyra_hof.cpp | 1933 ----------- engines/kyra/kyra_hof.h | 697 ---- engines/kyra/kyra_lok.cpp | 990 ------ engines/kyra/kyra_lok.h | 816 ----- engines/kyra/kyra_mr.cpp | 1426 -------- engines/kyra/kyra_mr.h | 670 ---- engines/kyra/kyra_rpg.cpp | 366 --- engines/kyra/kyra_rpg.h | 388 --- engines/kyra/kyra_v1.cpp | 698 ---- engines/kyra/kyra_v1.h | 4 +- engines/kyra/kyra_v2.cpp | 244 -- engines/kyra/kyra_v2.h | 400 --- engines/kyra/lol.cpp | 4513 -------------------------- engines/kyra/lol.h | 1345 -------- engines/kyra/magic_eob.cpp | 1381 -------- engines/kyra/module.mk | 216 +- engines/kyra/resource.cpp | 372 --- engines/kyra/resource.h | 1165 ------- engines/kyra/resource/resource.cpp | 372 +++ engines/kyra/resource/resource.h | 1165 +++++++ engines/kyra/resource/resource_intern.cpp | 1351 ++++++++ engines/kyra/resource/resource_intern.h | 146 + engines/kyra/resource/staticres.cpp | 2046 ++++++++++++ engines/kyra/resource/staticres_eob.cpp | 1379 ++++++++ engines/kyra/resource/staticres_lol.cpp | 857 +++++ engines/kyra/resource/staticres_rpg.cpp | 99 + engines/kyra/resource_intern.cpp | 1351 -------- engines/kyra/resource_intern.h | 146 - engines/kyra/saveload.cpp | 278 -- engines/kyra/saveload_eob.cpp | 1301 -------- engines/kyra/saveload_hof.cpp | 331 -- engines/kyra/saveload_lok.cpp | 319 -- engines/kyra/saveload_lol.cpp | 583 ---- engines/kyra/saveload_mr.cpp | 329 -- engines/kyra/saveload_rpg.cpp | 127 - engines/kyra/scene_eob.cpp | 862 ----- engines/kyra/scene_hof.cpp | 737 ----- engines/kyra/scene_lok.cpp | 1301 -------- engines/kyra/scene_lol.cpp | 1577 --------- engines/kyra/scene_mr.cpp | 787 ----- engines/kyra/scene_rpg.cpp | 658 ---- engines/kyra/scene_v1.cpp | 360 -- engines/kyra/scene_v2.cpp | 227 -- engines/kyra/screen.cpp | 3971 ---------------------- engines/kyra/screen.h | 736 ----- engines/kyra/screen_eob.cpp | 1974 ----------- engines/kyra/screen_eob.h | 177 - engines/kyra/screen_hof.cpp | 96 - engines/kyra/screen_hof.h | 50 - engines/kyra/screen_lok.cpp | 447 --- engines/kyra/screen_lok.h | 111 - engines/kyra/screen_lol.cpp | 893 ----- engines/kyra/screen_lol.h | 105 - engines/kyra/screen_mr.cpp | 130 - engines/kyra/screen_mr.h | 53 - engines/kyra/screen_v2.cpp | 461 --- engines/kyra/screen_v2.h | 81 - engines/kyra/script.cpp | 445 --- engines/kyra/script.h | 159 - engines/kyra/script/script.cpp | 445 +++ engines/kyra/script/script.h | 159 + engines/kyra/script/script_eob.cpp | 1622 +++++++++ engines/kyra/script/script_eob.h | 131 + engines/kyra/script/script_hof.cpp | 1727 ++++++++++ engines/kyra/script/script_lok.cpp | 1962 +++++++++++ engines/kyra/script/script_lol.cpp | 3051 +++++++++++++++++ engines/kyra/script/script_mr.cpp | 1377 ++++++++ engines/kyra/script/script_tim.cpp | 1101 +++++++ engines/kyra/script/script_tim.h | 306 ++ engines/kyra/script/script_v1.cpp | 125 + engines/kyra/script/script_v2.cpp | 342 ++ engines/kyra/script_eob.cpp | 1622 --------- engines/kyra/script_eob.h | 131 - engines/kyra/script_hof.cpp | 1727 ---------- engines/kyra/script_lok.cpp | 1962 ----------- engines/kyra/script_lol.cpp | 3051 ----------------- engines/kyra/script_mr.cpp | 1377 -------- engines/kyra/script_tim.cpp | 1101 ------- engines/kyra/script_tim.h | 306 -- engines/kyra/script_v1.cpp | 125 - engines/kyra/script_v2.cpp | 342 -- engines/kyra/seqplayer.cpp | 658 ---- engines/kyra/seqplayer.h | 124 - engines/kyra/sequence/seqplayer.cpp | 658 ++++ engines/kyra/sequence/seqplayer.h | 124 + engines/kyra/sequence/sequences_darkmoon.cpp | 1643 ++++++++++ engines/kyra/sequence/sequences_eob.cpp | 1152 +++++++ engines/kyra/sequence/sequences_hof.cpp | 3515 ++++++++++++++++++++ engines/kyra/sequence/sequences_hof.h | 74 + engines/kyra/sequence/sequences_lok.cpp | 2116 ++++++++++++ engines/kyra/sequence/sequences_lol.cpp | 1538 +++++++++ engines/kyra/sequence/sequences_mr.cpp | 237 ++ engines/kyra/sequence/sequences_v2.cpp | 130 + engines/kyra/sequences_darkmoon.cpp | 1643 ---------- engines/kyra/sequences_eob.cpp | 1152 ------- engines/kyra/sequences_hof.cpp | 3515 -------------------- engines/kyra/sequences_hof.h | 74 - engines/kyra/sequences_lok.cpp | 2116 ------------ engines/kyra/sequences_lol.cpp | 1538 --------- engines/kyra/sequences_mr.cpp | 237 -- engines/kyra/sequences_v2.cpp | 130 - engines/kyra/sound.cpp | 371 --- engines/kyra/sound.h | 341 -- engines/kyra/sound/sound.cpp | 371 +++ engines/kyra/sound/sound.h | 341 ++ engines/kyra/sound/sound_adlib.cpp | 2518 ++++++++++++++ engines/kyra/sound/sound_adlib.h | 115 + engines/kyra/sound/sound_amiga.cpp | 232 ++ engines/kyra/sound/sound_digital.cpp | 544 ++++ engines/kyra/sound/sound_digital.h | 119 + engines/kyra/sound/sound_intern.h | 407 +++ engines/kyra/sound/sound_lok.cpp | 96 + engines/kyra/sound/sound_lol.cpp | 307 ++ engines/kyra/sound/sound_midi.cpp | 814 +++++ engines/kyra/sound/sound_pcspk.cpp | 366 +++ engines/kyra/sound/sound_towns.cpp | 749 +++++ engines/kyra/sound/sound_towns_darkmoon.cpp | 278 ++ engines/kyra/sound_adlib.cpp | 2518 -------------- engines/kyra/sound_adlib.h | 115 - engines/kyra/sound_amiga.cpp | 232 -- engines/kyra/sound_digital.cpp | 544 ---- engines/kyra/sound_digital.h | 119 - engines/kyra/sound_intern.h | 407 --- engines/kyra/sound_lok.cpp | 96 - engines/kyra/sound_lol.cpp | 307 -- engines/kyra/sound_midi.cpp | 814 ----- engines/kyra/sound_pcspk.cpp | 366 --- engines/kyra/sound_towns.cpp | 749 ----- engines/kyra/sound_towns_darkmoon.cpp | 278 -- engines/kyra/sprites.cpp | 575 ---- engines/kyra/sprites.h | 99 - engines/kyra/sprites_eob.cpp | 1285 -------- engines/kyra/sprites_lol.cpp | 1558 --------- engines/kyra/sprites_rpg.cpp | 46 - engines/kyra/staticres.cpp | 2046 ------------ engines/kyra/staticres_eob.cpp | 1379 -------- engines/kyra/staticres_lol.cpp | 857 ----- engines/kyra/staticres_rpg.cpp | 99 - engines/kyra/text.cpp | 330 -- engines/kyra/text.h | 81 - engines/kyra/text/text.cpp | 330 ++ engines/kyra/text/text.h | 81 + engines/kyra/text/text_hof.cpp | 672 ++++ engines/kyra/text/text_hof.h | 52 + engines/kyra/text/text_lok.cpp | 396 +++ engines/kyra/text/text_lol.cpp | 348 ++ engines/kyra/text/text_lol.h | 70 + engines/kyra/text/text_mr.cpp | 894 +++++ engines/kyra/text/text_mr.h | 52 + engines/kyra/text/text_rpg.cpp | 739 +++++ engines/kyra/text/text_rpg.h | 115 + engines/kyra/text_hof.cpp | 672 ---- engines/kyra/text_hof.h | 52 - engines/kyra/text_lok.cpp | 396 --- engines/kyra/text_lol.cpp | 348 -- engines/kyra/text_lol.h | 70 - engines/kyra/text_mr.cpp | 894 ----- engines/kyra/text_mr.h | 52 - engines/kyra/text_rpg.cpp | 739 ----- engines/kyra/text_rpg.h | 115 - engines/kyra/timer.cpp | 304 -- engines/kyra/timer.h | 106 - engines/kyra/timer_eob.cpp | 361 -- engines/kyra/timer_hof.cpp | 110 - engines/kyra/timer_lok.cpp | 192 -- engines/kyra/timer_lol.cpp | 206 -- engines/kyra/timer_mr.cpp | 101 - engines/kyra/timer_rpg.cpp | 90 - engines/kyra/util.cpp | 148 - engines/kyra/util.h | 48 - engines/kyra/vqa.cpp | 667 ---- engines/kyra/vqa.h | 161 - engines/kyra/wsamovie.cpp | 463 --- engines/kyra/wsamovie.h | 134 - 315 files changed, 111094 insertions(+), 111094 deletions(-) delete mode 100644 engines/kyra/animator_hof.cpp delete mode 100644 engines/kyra/animator_lok.cpp delete mode 100644 engines/kyra/animator_lok.h delete mode 100644 engines/kyra/animator_mr.cpp delete mode 100644 engines/kyra/animator_tim.cpp delete mode 100644 engines/kyra/animator_v2.cpp delete mode 100644 engines/kyra/chargen.cpp delete mode 100644 engines/kyra/darkmoon.cpp delete mode 100644 engines/kyra/darkmoon.h delete mode 100644 engines/kyra/debugger.cpp delete mode 100644 engines/kyra/debugger.h create mode 100644 engines/kyra/engine/chargen.cpp create mode 100644 engines/kyra/engine/darkmoon.cpp create mode 100644 engines/kyra/engine/darkmoon.h create mode 100644 engines/kyra/engine/eob.cpp create mode 100644 engines/kyra/engine/eob.h create mode 100644 engines/kyra/engine/eobcommon.cpp create mode 100644 engines/kyra/engine/eobcommon.h create mode 100644 engines/kyra/engine/item.h create mode 100644 engines/kyra/engine/items_eob.cpp create mode 100644 engines/kyra/engine/items_hof.cpp create mode 100644 engines/kyra/engine/items_lok.cpp create mode 100644 engines/kyra/engine/items_lol.cpp create mode 100644 engines/kyra/engine/items_mr.cpp create mode 100644 engines/kyra/engine/items_v2.cpp create mode 100644 engines/kyra/engine/kyra_hof.cpp create mode 100644 engines/kyra/engine/kyra_hof.h create mode 100644 engines/kyra/engine/kyra_lok.cpp create mode 100644 engines/kyra/engine/kyra_lok.h create mode 100644 engines/kyra/engine/kyra_mr.cpp create mode 100644 engines/kyra/engine/kyra_mr.h create mode 100644 engines/kyra/engine/kyra_rpg.cpp create mode 100644 engines/kyra/engine/kyra_rpg.h create mode 100644 engines/kyra/engine/kyra_v1.cpp create mode 100644 engines/kyra/engine/kyra_v2.cpp create mode 100644 engines/kyra/engine/kyra_v2.h create mode 100644 engines/kyra/engine/lol.cpp create mode 100644 engines/kyra/engine/lol.h create mode 100644 engines/kyra/engine/magic_eob.cpp create mode 100644 engines/kyra/engine/scene_eob.cpp create mode 100644 engines/kyra/engine/scene_hof.cpp create mode 100644 engines/kyra/engine/scene_lok.cpp create mode 100644 engines/kyra/engine/scene_lol.cpp create mode 100644 engines/kyra/engine/scene_mr.cpp create mode 100644 engines/kyra/engine/scene_rpg.cpp create mode 100644 engines/kyra/engine/scene_v1.cpp create mode 100644 engines/kyra/engine/scene_v2.cpp create mode 100644 engines/kyra/engine/sprites.cpp create mode 100644 engines/kyra/engine/sprites.h create mode 100644 engines/kyra/engine/sprites_eob.cpp create mode 100644 engines/kyra/engine/sprites_lol.cpp create mode 100644 engines/kyra/engine/sprites_rpg.cpp create mode 100644 engines/kyra/engine/timer.cpp create mode 100644 engines/kyra/engine/timer.h create mode 100644 engines/kyra/engine/timer_eob.cpp create mode 100644 engines/kyra/engine/timer_hof.cpp create mode 100644 engines/kyra/engine/timer_lok.cpp create mode 100644 engines/kyra/engine/timer_lol.cpp create mode 100644 engines/kyra/engine/timer_mr.cpp create mode 100644 engines/kyra/engine/timer_rpg.cpp create mode 100644 engines/kyra/engine/util.cpp create mode 100644 engines/kyra/engine/util.h delete mode 100644 engines/kyra/eob.cpp delete mode 100644 engines/kyra/eob.h delete mode 100644 engines/kyra/eobcommon.cpp delete mode 100644 engines/kyra/eobcommon.h create mode 100644 engines/kyra/graphics/animator_hof.cpp create mode 100644 engines/kyra/graphics/animator_lok.cpp create mode 100644 engines/kyra/graphics/animator_lok.h create mode 100644 engines/kyra/graphics/animator_mr.cpp create mode 100644 engines/kyra/graphics/animator_tim.cpp create mode 100644 engines/kyra/graphics/animator_v2.cpp create mode 100644 engines/kyra/graphics/screen.cpp create mode 100644 engines/kyra/graphics/screen.h create mode 100644 engines/kyra/graphics/screen_eob.cpp create mode 100644 engines/kyra/graphics/screen_eob.h create mode 100644 engines/kyra/graphics/screen_hof.cpp create mode 100644 engines/kyra/graphics/screen_hof.h create mode 100644 engines/kyra/graphics/screen_lok.cpp create mode 100644 engines/kyra/graphics/screen_lok.h create mode 100644 engines/kyra/graphics/screen_lol.cpp create mode 100644 engines/kyra/graphics/screen_lol.h create mode 100644 engines/kyra/graphics/screen_mr.cpp create mode 100644 engines/kyra/graphics/screen_mr.h create mode 100644 engines/kyra/graphics/screen_v2.cpp create mode 100644 engines/kyra/graphics/screen_v2.h create mode 100644 engines/kyra/graphics/vqa.cpp create mode 100644 engines/kyra/graphics/vqa.h create mode 100644 engines/kyra/graphics/wsamovie.cpp create mode 100644 engines/kyra/graphics/wsamovie.h delete mode 100644 engines/kyra/gui.cpp delete mode 100644 engines/kyra/gui.h create mode 100644 engines/kyra/gui/debugger.cpp create mode 100644 engines/kyra/gui/debugger.h create mode 100644 engines/kyra/gui/gui.cpp create mode 100644 engines/kyra/gui/gui.h create mode 100644 engines/kyra/gui/gui_eob.cpp create mode 100644 engines/kyra/gui/gui_eob.h create mode 100644 engines/kyra/gui/gui_hof.cpp create mode 100644 engines/kyra/gui/gui_hof.h create mode 100644 engines/kyra/gui/gui_lok.cpp create mode 100644 engines/kyra/gui/gui_lok.h create mode 100644 engines/kyra/gui/gui_lol.cpp create mode 100644 engines/kyra/gui/gui_lol.h create mode 100644 engines/kyra/gui/gui_mr.cpp create mode 100644 engines/kyra/gui/gui_mr.h create mode 100644 engines/kyra/gui/gui_rpg.cpp create mode 100644 engines/kyra/gui/gui_v1.cpp create mode 100644 engines/kyra/gui/gui_v1.h create mode 100644 engines/kyra/gui/gui_v2.cpp create mode 100644 engines/kyra/gui/gui_v2.h create mode 100644 engines/kyra/gui/saveload.cpp create mode 100644 engines/kyra/gui/saveload_eob.cpp create mode 100644 engines/kyra/gui/saveload_hof.cpp create mode 100644 engines/kyra/gui/saveload_lok.cpp create mode 100644 engines/kyra/gui/saveload_lol.cpp create mode 100644 engines/kyra/gui/saveload_mr.cpp create mode 100644 engines/kyra/gui/saveload_rpg.cpp delete mode 100644 engines/kyra/gui_eob.cpp delete mode 100644 engines/kyra/gui_eob.h delete mode 100644 engines/kyra/gui_hof.cpp delete mode 100644 engines/kyra/gui_hof.h delete mode 100644 engines/kyra/gui_lok.cpp delete mode 100644 engines/kyra/gui_lok.h delete mode 100644 engines/kyra/gui_lol.cpp delete mode 100644 engines/kyra/gui_lol.h delete mode 100644 engines/kyra/gui_mr.cpp delete mode 100644 engines/kyra/gui_mr.h delete mode 100644 engines/kyra/gui_rpg.cpp delete mode 100644 engines/kyra/gui_v1.cpp delete mode 100644 engines/kyra/gui_v1.h delete mode 100644 engines/kyra/gui_v2.cpp delete mode 100644 engines/kyra/gui_v2.h delete mode 100644 engines/kyra/item.h delete mode 100644 engines/kyra/items_eob.cpp delete mode 100644 engines/kyra/items_hof.cpp delete mode 100644 engines/kyra/items_lok.cpp delete mode 100644 engines/kyra/items_lol.cpp delete mode 100644 engines/kyra/items_mr.cpp delete mode 100644 engines/kyra/items_v2.cpp delete mode 100644 engines/kyra/kyra_hof.cpp delete mode 100644 engines/kyra/kyra_hof.h delete mode 100644 engines/kyra/kyra_lok.cpp delete mode 100644 engines/kyra/kyra_lok.h delete mode 100644 engines/kyra/kyra_mr.cpp delete mode 100644 engines/kyra/kyra_mr.h delete mode 100644 engines/kyra/kyra_rpg.cpp delete mode 100644 engines/kyra/kyra_rpg.h delete mode 100644 engines/kyra/kyra_v1.cpp delete mode 100644 engines/kyra/kyra_v2.cpp delete mode 100644 engines/kyra/kyra_v2.h delete mode 100644 engines/kyra/lol.cpp delete mode 100644 engines/kyra/lol.h delete mode 100644 engines/kyra/magic_eob.cpp delete mode 100644 engines/kyra/resource.cpp delete mode 100644 engines/kyra/resource.h create mode 100644 engines/kyra/resource/resource.cpp create mode 100644 engines/kyra/resource/resource.h create mode 100644 engines/kyra/resource/resource_intern.cpp create mode 100644 engines/kyra/resource/resource_intern.h create mode 100644 engines/kyra/resource/staticres.cpp create mode 100644 engines/kyra/resource/staticres_eob.cpp create mode 100644 engines/kyra/resource/staticres_lol.cpp create mode 100644 engines/kyra/resource/staticres_rpg.cpp delete mode 100644 engines/kyra/resource_intern.cpp delete mode 100644 engines/kyra/resource_intern.h delete mode 100644 engines/kyra/saveload.cpp delete mode 100644 engines/kyra/saveload_eob.cpp delete mode 100644 engines/kyra/saveload_hof.cpp delete mode 100644 engines/kyra/saveload_lok.cpp delete mode 100644 engines/kyra/saveload_lol.cpp delete mode 100644 engines/kyra/saveload_mr.cpp delete mode 100644 engines/kyra/saveload_rpg.cpp delete mode 100644 engines/kyra/scene_eob.cpp delete mode 100644 engines/kyra/scene_hof.cpp delete mode 100644 engines/kyra/scene_lok.cpp delete mode 100644 engines/kyra/scene_lol.cpp delete mode 100644 engines/kyra/scene_mr.cpp delete mode 100644 engines/kyra/scene_rpg.cpp delete mode 100644 engines/kyra/scene_v1.cpp delete mode 100644 engines/kyra/scene_v2.cpp delete mode 100644 engines/kyra/screen.cpp delete mode 100644 engines/kyra/screen.h delete mode 100644 engines/kyra/screen_eob.cpp delete mode 100644 engines/kyra/screen_eob.h delete mode 100644 engines/kyra/screen_hof.cpp delete mode 100644 engines/kyra/screen_hof.h delete mode 100644 engines/kyra/screen_lok.cpp delete mode 100644 engines/kyra/screen_lok.h delete mode 100644 engines/kyra/screen_lol.cpp delete mode 100644 engines/kyra/screen_lol.h delete mode 100644 engines/kyra/screen_mr.cpp delete mode 100644 engines/kyra/screen_mr.h delete mode 100644 engines/kyra/screen_v2.cpp delete mode 100644 engines/kyra/screen_v2.h delete mode 100644 engines/kyra/script.cpp delete mode 100644 engines/kyra/script.h create mode 100644 engines/kyra/script/script.cpp create mode 100644 engines/kyra/script/script.h create mode 100644 engines/kyra/script/script_eob.cpp create mode 100644 engines/kyra/script/script_eob.h create mode 100644 engines/kyra/script/script_hof.cpp create mode 100644 engines/kyra/script/script_lok.cpp create mode 100644 engines/kyra/script/script_lol.cpp create mode 100644 engines/kyra/script/script_mr.cpp create mode 100644 engines/kyra/script/script_tim.cpp create mode 100644 engines/kyra/script/script_tim.h create mode 100644 engines/kyra/script/script_v1.cpp create mode 100644 engines/kyra/script/script_v2.cpp delete mode 100644 engines/kyra/script_eob.cpp delete mode 100644 engines/kyra/script_eob.h delete mode 100644 engines/kyra/script_hof.cpp delete mode 100644 engines/kyra/script_lok.cpp delete mode 100644 engines/kyra/script_lol.cpp delete mode 100644 engines/kyra/script_mr.cpp delete mode 100644 engines/kyra/script_tim.cpp delete mode 100644 engines/kyra/script_tim.h delete mode 100644 engines/kyra/script_v1.cpp delete mode 100644 engines/kyra/script_v2.cpp delete mode 100644 engines/kyra/seqplayer.cpp delete mode 100644 engines/kyra/seqplayer.h create mode 100644 engines/kyra/sequence/seqplayer.cpp create mode 100644 engines/kyra/sequence/seqplayer.h create mode 100644 engines/kyra/sequence/sequences_darkmoon.cpp create mode 100644 engines/kyra/sequence/sequences_eob.cpp create mode 100644 engines/kyra/sequence/sequences_hof.cpp create mode 100644 engines/kyra/sequence/sequences_hof.h create mode 100644 engines/kyra/sequence/sequences_lok.cpp create mode 100644 engines/kyra/sequence/sequences_lol.cpp create mode 100644 engines/kyra/sequence/sequences_mr.cpp create mode 100644 engines/kyra/sequence/sequences_v2.cpp delete mode 100644 engines/kyra/sequences_darkmoon.cpp delete mode 100644 engines/kyra/sequences_eob.cpp delete mode 100644 engines/kyra/sequences_hof.cpp delete mode 100644 engines/kyra/sequences_hof.h delete mode 100644 engines/kyra/sequences_lok.cpp delete mode 100644 engines/kyra/sequences_lol.cpp delete mode 100644 engines/kyra/sequences_mr.cpp delete mode 100644 engines/kyra/sequences_v2.cpp delete mode 100644 engines/kyra/sound.cpp delete mode 100644 engines/kyra/sound.h create mode 100644 engines/kyra/sound/sound.cpp create mode 100644 engines/kyra/sound/sound.h create mode 100644 engines/kyra/sound/sound_adlib.cpp create mode 100644 engines/kyra/sound/sound_adlib.h create mode 100644 engines/kyra/sound/sound_amiga.cpp create mode 100644 engines/kyra/sound/sound_digital.cpp create mode 100644 engines/kyra/sound/sound_digital.h create mode 100644 engines/kyra/sound/sound_intern.h create mode 100644 engines/kyra/sound/sound_lok.cpp create mode 100644 engines/kyra/sound/sound_lol.cpp create mode 100644 engines/kyra/sound/sound_midi.cpp create mode 100644 engines/kyra/sound/sound_pcspk.cpp create mode 100644 engines/kyra/sound/sound_towns.cpp create mode 100644 engines/kyra/sound/sound_towns_darkmoon.cpp delete mode 100644 engines/kyra/sound_adlib.cpp delete mode 100644 engines/kyra/sound_adlib.h delete mode 100644 engines/kyra/sound_amiga.cpp delete mode 100644 engines/kyra/sound_digital.cpp delete mode 100644 engines/kyra/sound_digital.h delete mode 100644 engines/kyra/sound_intern.h delete mode 100644 engines/kyra/sound_lok.cpp delete mode 100644 engines/kyra/sound_lol.cpp delete mode 100644 engines/kyra/sound_midi.cpp delete mode 100644 engines/kyra/sound_pcspk.cpp delete mode 100644 engines/kyra/sound_towns.cpp delete mode 100644 engines/kyra/sound_towns_darkmoon.cpp delete mode 100644 engines/kyra/sprites.cpp delete mode 100644 engines/kyra/sprites.h delete mode 100644 engines/kyra/sprites_eob.cpp delete mode 100644 engines/kyra/sprites_lol.cpp delete mode 100644 engines/kyra/sprites_rpg.cpp delete mode 100644 engines/kyra/staticres.cpp delete mode 100644 engines/kyra/staticres_eob.cpp delete mode 100644 engines/kyra/staticres_lol.cpp delete mode 100644 engines/kyra/staticres_rpg.cpp delete mode 100644 engines/kyra/text.cpp delete mode 100644 engines/kyra/text.h create mode 100644 engines/kyra/text/text.cpp create mode 100644 engines/kyra/text/text.h create mode 100644 engines/kyra/text/text_hof.cpp create mode 100644 engines/kyra/text/text_hof.h create mode 100644 engines/kyra/text/text_lok.cpp create mode 100644 engines/kyra/text/text_lol.cpp create mode 100644 engines/kyra/text/text_lol.h create mode 100644 engines/kyra/text/text_mr.cpp create mode 100644 engines/kyra/text/text_mr.h create mode 100644 engines/kyra/text/text_rpg.cpp create mode 100644 engines/kyra/text/text_rpg.h delete mode 100644 engines/kyra/text_hof.cpp delete mode 100644 engines/kyra/text_hof.h delete mode 100644 engines/kyra/text_lok.cpp delete mode 100644 engines/kyra/text_lol.cpp delete mode 100644 engines/kyra/text_lol.h delete mode 100644 engines/kyra/text_mr.cpp delete mode 100644 engines/kyra/text_mr.h delete mode 100644 engines/kyra/text_rpg.cpp delete mode 100644 engines/kyra/text_rpg.h delete mode 100644 engines/kyra/timer.cpp delete mode 100644 engines/kyra/timer.h delete mode 100644 engines/kyra/timer_eob.cpp delete mode 100644 engines/kyra/timer_hof.cpp delete mode 100644 engines/kyra/timer_lok.cpp delete mode 100644 engines/kyra/timer_lol.cpp delete mode 100644 engines/kyra/timer_mr.cpp delete mode 100644 engines/kyra/timer_rpg.cpp delete mode 100644 engines/kyra/util.cpp delete mode 100644 engines/kyra/util.h delete mode 100644 engines/kyra/vqa.cpp delete mode 100644 engines/kyra/vqa.h delete mode 100644 engines/kyra/wsamovie.cpp delete mode 100644 engines/kyra/wsamovie.h (limited to 'engines') diff --git a/engines/kyra/animator_hof.cpp b/engines/kyra/animator_hof.cpp deleted file mode 100644 index 7ce79cb7a0..0000000000 --- a/engines/kyra/animator_hof.cpp +++ /dev/null @@ -1,313 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "kyra/kyra_hof.h" -#include "kyra/wsamovie.h" - -#include "common/system.h" - -namespace Kyra { - -void KyraEngine_HoF::restorePage3() { - screen()->copyBlockToPage(2, 0, 0, 320, 144, _gamePlayBuffer); -} - -void KyraEngine_HoF::clearAnimObjects() { - _animObjects[0].index = 0; - _animObjects[0].type = 0; - _animObjects[0].enabled = 1; - _animObjects[0].flags = 0x800; - _animObjects[0].width = 32; - _animObjects[0].height = 49; - _animObjects[0].width2 = 4; - _animObjects[0].height2 = 10; - - for (int i = 1; i < 11; ++i) { - _animObjects[i].index = i; - _animObjects[i].type = 2; - } - - for (int i = 11; i <= 40; ++i) { - _animObjects[i].index = i; - _animObjects[i].type = 1; - _animObjects[i].flags = 0x800; - _animObjects[i].width = 16; - _animObjects[i].height = 16; - } -} - -void KyraEngine_HoF::drawAnimObjects() { - for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { - if (!curObject->enabled) - continue; - - int x = curObject->xPos2 - (_screen->getScreenDim(2)->sx << 3); - int y = curObject->yPos2 - _screen->getScreenDim(2)->sy; - int layer = 7; - - if (curObject->flags & 0x800) { - if (curObject->animFlags) - layer = 0; - else - layer = getDrawLayer(curObject->xPos1, curObject->yPos1); - } - curObject->flags |= 0x800; - - if (curObject->index) - drawSceneAnimObject(curObject, x, y, layer); - else - drawCharacterAnimObject(curObject, x, y, layer); - } -} - -void KyraEngine_HoF::refreshAnimObjects(int force) { - for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { - if (!curObject->enabled) - continue; - if (!curObject->needRefresh && !force) - continue; - - int x = curObject->xPos2 - curObject->width2; - if (x < 0) - x = 0; - if (x >= 320) - x = 319; - int y = curObject->yPos2 - curObject->height2; - if (y < 0) - y = 0; - if (y >= 143) - y = 142; - - int width = curObject->width + curObject->width2 + 8; - int height = curObject->height + curObject->height2*2; - if (width + x > 320) - width -= width + x - 322; - if (height + y > 143) - height -= height + y - 144; - - _screen->copyRegion(x, y, x, y, width, height, 2, 0, Screen::CR_NO_P_CHECK); - - curObject->needRefresh = false; - } -} - -void KyraEngine_HoF::updateItemAnimations() { - bool nextFrame = false; - - if (_itemAnimDefinition[0].itemIndex == -1 || _inventorySaved) - return; - - const ItemAnimDefinition *s = &_itemAnimDefinition[_nextAnimItem]; - ActiveItemAnim *a = &_activeItemAnim[_nextAnimItem]; - _nextAnimItem = (_nextAnimItem + 1) % _itemAnimDefinitionSize; - - if (_system->getMillis() < a->nextFrameTime) - return; - - uint16 shpIdx = s->frames[a->currentFrame].index + 64; - if (s->itemIndex == _mouseState && s->itemIndex == _itemInHand && _screen->isMouseVisible()) { - nextFrame = true; - _screen->setMouseCursor(8, 15, getShapePtr(shpIdx)); - } - - for (int i = 0; i < 10; i++) { - if (s->itemIndex == _mainCharacter.inventory[i]) { - nextFrame = true; - _screen->drawShape(2, getShapePtr(240 + i), 304, 184, 0, 0); - _screen->drawShape(2, getShapePtr(shpIdx), 304, 184, 0, 0); - _screen->copyRegion(304, 184, _inventoryX[i], _inventoryY[i], 16, 16, 2, 0); - } - } - - _screen->updateScreen(); - - for (int i = 11; i < 40; i++) { - AnimObj *animObject = &_animObjects[i]; - if (animObject->shapeIndex2 == s->itemIndex + 64) { - if (s->itemIndex == 121) { - int f = findItem(_mainCharacter.sceneId, 121); - int nx = _itemList[f].x - 4; - if (nx > 12) { - if (lineIsPassable(nx, _itemList[f].y)) { - animObject->xPos2 -= 4; - _itemList[f].x -= 4; - } - } - } - animObject->shapePtr = getShapePtr(shpIdx); - animObject->shapeIndex1 = shpIdx; - animObject->needRefresh = 1; - nextFrame = true; - } - } - - if (nextFrame) { - a->nextFrameTime = _system->getMillis() + (s->frames[a->currentFrame].delay * _tickLength); - a->currentFrame = (a->currentFrame + 1) % s->numFrames; - } -} - -void KyraEngine_HoF::updateCharFacing() { - if (_mainCharacter.x1 > _mouseX) - _mainCharacter.facing = 5; - else - _mainCharacter.facing = 3; - - _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; - updateCharacterAnim(0); - refreshAnimObjectsIfNeed(); -} - -void KyraEngine_HoF::updateCharacterAnim(int) { - Character *c = &_mainCharacter; - AnimObj *animState = _animObjects; - - animState->needRefresh = 1; - animState->specialRefresh = 1; - - if (c->facing >= 1 && c->facing <= 3) - animState->flags |= 1; - else if (c->facing >= 5 && c->facing <= 7) - animState->flags &= ~1; - - animState->xPos2 = animState->xPos1 = c->x1; - animState->yPos2 = animState->yPos1 = c->y1; - animState->shapePtr = getShapePtr(c->animFrame); - animState->shapeIndex1 = animState->shapeIndex2 = c->animFrame; - - int xAdd = _shapeDescTable[c->animFrame-9].xAdd; - int yAdd = _shapeDescTable[c->animFrame-9].yAdd; - - _charScale = getScale(c->x1, c->y1); - - animState->xPos2 += (xAdd * _charScale) >> 8; - animState->yPos2 += (yAdd * _charScale) >> 8; - animState->width2 = 8; - animState->height2 = 10; - - _animList = deleteAnimListEntry(_animList, animState); - if (_animList) - _animList = addToAnimListSorted(_animList, animState); - else - _animList = initAnimList(_animList, animState); - - updateCharPal(1); -} - -void KyraEngine_HoF::updateSceneAnim(int anim, int newFrame) { - AnimObj *animObject = &_animObjects[1+anim]; - if (!animObject->enabled) - return; - - animObject->needRefresh = 1; - animObject->specialRefresh = 1; - animObject->flags = 0; - - if (_sceneAnims[anim].flags & 2) - animObject->flags |= 0x800; - else - animObject->flags &= ~0x800; - - if (_sceneAnims[anim].flags & 4) - animObject->flags |= 1; - else - animObject->flags &= ~1; - - if (_sceneAnims[anim].flags & 0x20) { - animObject->shapePtr = _sceneShapeTable[newFrame]; - animObject->shapeIndex2 = 0xFFFF; - animObject->shapeIndex3 = 0xFFFF; - animObject->animNum = 0xFFFF; - } else { - animObject->shapePtr = 0; - animObject->shapeIndex3 = newFrame; - animObject->animNum = anim; - } - - animObject->xPos1 = _sceneAnims[anim].x; - animObject->yPos1 = _sceneAnims[anim].y; - animObject->xPos2 = _sceneAnims[anim].x2; - animObject->yPos2 = _sceneAnims[anim].y2; - - if (_sceneAnims[anim].flags & 2) { - _animList = deleteAnimListEntry(_animList, animObject); - if (!_animList) - _animList = initAnimList(_animList, animObject); - else - _animList = addToAnimListSorted(_animList, animObject); - } -} - -void KyraEngine_HoF::drawSceneAnimObject(AnimObj *obj, int x, int y, int layer) { - if (obj->type == 1) { - if (obj->shapeIndex1 == 0xFFFF) - return; - int scale = getScale(obj->xPos1, obj->yPos1); - _screen->drawShape(2, getShapePtr(obj->shapeIndex1), x, y, 2, obj->flags | 4, layer, scale, scale); - return; - } - - if (obj->shapePtr) { - _screen->drawShape(2, obj->shapePtr, x, y, 2, obj->flags, layer); - } else { - if (obj->shapeIndex3 == 0xFFFF || obj->animNum == 0xFFFF) - return; - - int flags = 0x4000; - if (obj->flags & 0x800) - flags |= 0x8000; - - if (_sceneAnims[obj->animNum].wsaFlag) { - x = y = 0; - } else { - x = obj->xPos2; - y = obj->yPos2; - } - - _sceneAnimMovie[obj->animNum]->displayFrame(obj->shapeIndex3, 2, x, y, int(flags | layer), 0, 0); - } -} - -void KyraEngine_HoF::drawCharacterAnimObject(AnimObj *obj, int x, int y, int layer) { - if (_drawNoShapeFlag || obj->shapeIndex1 == 0xFFFF) - return; - _screen->drawShape(2, getShapePtr(obj->shapeIndex1), x, y, 2, obj->flags | 4, layer, _charScale, _charScale); -} - -void KyraEngine_HoF::setCharacterAnimDim(int w, int h) { - restorePage3(); - - _animObj0Width = _animObjects[0].width; - _animObj0Height = _animObjects[0].height; - - _animObjects[0].width = w; - _animObjects[0].height = h; -} - -void KyraEngine_HoF::resetCharacterAnimDim() { - restorePage3(); - - _animObjects[0].width = _animObj0Width; - _animObjects[0].height = _animObj0Height; -} - -} // End of namespace Kyra diff --git a/engines/kyra/animator_lok.cpp b/engines/kyra/animator_lok.cpp deleted file mode 100644 index ba6dc91e1f..0000000000 --- a/engines/kyra/animator_lok.cpp +++ /dev/null @@ -1,651 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "kyra/kyra_lok.h" -#include "kyra/screen.h" -#include "kyra/animator_lok.h" -#include "kyra/sprites.h" - -namespace Kyra { - -Animator_LoK::Animator_LoK(KyraEngine_LoK *vm, OSystem *system) { - _vm = vm; - _screen = vm->screen(); - _initOk = false; - _system = system; - _screenObjects = _actors = _items = _sprites = _objectQueue = 0; - _noDrawShapesFlag = 0; - - _actorBkgBackUp[0] = new uint8[_screen->getRectSize(8, 69)]; - memset(_actorBkgBackUp[0], 0, _screen->getRectSize(8, 69)); - _actorBkgBackUp[1] = new uint8[_screen->getRectSize(8, 69)]; - memset(_actorBkgBackUp[1], 0, _screen->getRectSize(8, 69)); -} - -Animator_LoK::~Animator_LoK() { - close(); - delete[] _actorBkgBackUp[0]; - delete[] _actorBkgBackUp[1]; -} - -void Animator_LoK::init(int actors_, int items_, int sprites_) { - _screenObjects = new AnimObject[actors_ + items_ + sprites_]; - assert(_screenObjects); - memset(_screenObjects, 0, sizeof(AnimObject) * (actors_ + items_ + sprites_)); - _actors = _screenObjects; - _sprites = &_screenObjects[actors_]; - _items = &_screenObjects[actors_ + items_]; - _brandonDrawFrame = 113; - - _initOk = true; -} - -void Animator_LoK::close() { - if (_initOk) { - _initOk = false; - delete[] _screenObjects; - _screenObjects = _actors = _items = _sprites = _objectQueue = 0; - } -} - -void Animator_LoK::initAnimStateList() { - AnimObject *animStates = _screenObjects; - animStates[0].index = 0; - animStates[0].active = 1; - animStates[0].flags = 0x800; - animStates[0].background = _actorBkgBackUp[0]; - animStates[0].rectSize = _screen->getRectSize(4, 48); - animStates[0].width = 4; - animStates[0].height = 48; - animStates[0].width2 = 4; - animStates[0].height2 = 3; - - for (int i = 1; i <= 4; ++i) { - animStates[i].index = i; - animStates[i].active = 0; - animStates[i].flags = 0x800; - animStates[i].background = _actorBkgBackUp[1]; - animStates[i].rectSize = _screen->getRectSize(4, 64); - animStates[i].width = 4; - animStates[i].height = 48; - animStates[i].width2 = 4; - animStates[i].height2 = 3; - } - - for (int i = 5; i < 16; ++i) { - animStates[i].index = i; - animStates[i].active = 0; - animStates[i].flags = 0; - } - - for (int i = 16; i < 28; ++i) { - animStates[i].index = i; - animStates[i].flags = 0; - animStates[i].background = _vm->_shapes[345 + i]; - animStates[i].rectSize = _screen->getRectSize(3, 24); - animStates[i].width = 3; - animStates[i].height = 16; - animStates[i].width2 = 0; - animStates[i].height2 = 0; - } -} - -void Animator_LoK::preserveAllBackgrounds() { - uint8 curPage = _screen->_curPage; - _screen->_curPage = 2; - - AnimObject *curObject = _objectQueue; - while (curObject) { - if (curObject->active && !curObject->disable) { - preserveOrRestoreBackground(curObject, false); - curObject->bkgdChangeFlag = 0; - } - curObject = curObject->nextAnimObject; - } - _screen->_curPage = curPage; -} - -void Animator_LoK::flagAllObjectsForBkgdChange() { - AnimObject *curObject = _objectQueue; - while (curObject) { - curObject->bkgdChangeFlag = 1; - curObject = curObject->nextAnimObject; - } -} - -void Animator_LoK::flagAllObjectsForRefresh() { - AnimObject *curObject = _objectQueue; - while (curObject) { - curObject->refreshFlag = 1; - curObject = curObject->nextAnimObject; - } -} - -void Animator_LoK::restoreAllObjectBackgrounds() { - AnimObject *curObject = _objectQueue; - _screen->_curPage = 2; - - while (curObject) { - if (curObject->active && !curObject->disable) { - preserveOrRestoreBackground(curObject, true); - curObject->x2 = curObject->x1; - curObject->y2 = curObject->y1; - } - curObject = curObject->nextAnimObject; - } - - _screen->_curPage = 0; -} - -void Animator_LoK::preserveAnyChangedBackgrounds() { - AnimObject *curObject = _objectQueue; - _screen->_curPage = 2; - - while (curObject) { - if (curObject->active && !curObject->disable && curObject->bkgdChangeFlag) { - preserveOrRestoreBackground(curObject, false); - curObject->bkgdChangeFlag = 0; - } - curObject = curObject->nextAnimObject; - } - - _screen->_curPage = 0; -} - -void Animator_LoK::preserveOrRestoreBackground(AnimObject *obj, bool restore) { - int x = 0, y = 0, width = obj->width, height = obj->height; - - if (restore) { - x = obj->x2 >> 3; - y = obj->y2; - } else { - x = obj->x1 >> 3; - y = obj->y1; - } - - if (x < 0) - x = 0; - if (y < 0) - y = 0; - - int temp; - - temp = x + width; - if (temp >= 39) - x = 39 - width; - temp = y + height; - if (temp >= 136) - y = 136 - height; - - if (restore) - _screen->copyBlockToPage(_screen->_curPage, x << 3, y, width << 3, height, obj->background); - else - _screen->copyRegionToBuffer(_screen->_curPage, x << 3, y, width << 3, height, obj->background); -} - -void Animator_LoK::prepDrawAllObjects() { - AnimObject *curObject = _objectQueue; - int drawPage = 2; - int flagUnk1 = 0, flagUnk2 = 0, flagUnk3 = 0; - if (_noDrawShapesFlag) - return; - if (_vm->_brandonStatusBit & 0x20) - flagUnk1 = 0x200; - if (_vm->_brandonStatusBit & 0x40) - flagUnk2 = 0x4000; - - while (curObject) { - if (curObject->active) { - int xpos = curObject->x1; - int ypos = curObject->y1; - - int drawLayer = 0; - if (!(curObject->flags & 0x800)) - drawLayer = 7; - else if (curObject->disable) - drawLayer = 0; - else - drawLayer = _vm->_sprites->getDrawLayer(curObject->drawY); - - // talking head functionallity - if (_vm->_talkingCharNum != -1 && (_vm->_currentCharacter->currentAnimFrame != 88 || curObject->index != 0)) { - const int16 baseAnimFrameTable1[] = { 0x11, 0x35, 0x59, 0x00, 0x00, 0x00 }; - const int16 baseAnimFrameTable2[] = { 0x15, 0x39, 0x5D, 0x00, 0x00, 0x00 }; - const int8 xOffsetTable1[] = { 2, 4, 0, 5, 2, 0, 0, 0 }; - const int8 xOffsetTable2[] = { 6, 4, 8, 3, 6, 0, 0, 0 }; - const int8 yOffsetTable1[] = { 0, 8, 1, 1, 0, 0, 0, 0 }; - const int8 yOffsetTable2[] = { 0, 8, 1, 1, 0, 0, 0, 0 }; - if (curObject->index == 0 || curObject->index <= 4) { - int shapesIndex = 0; - if (curObject->index == _vm->_charSayUnk3) { - shapesIndex = _vm->_currHeadShape + baseAnimFrameTable1[curObject->index]; - } else { - shapesIndex = baseAnimFrameTable2[curObject->index]; - int temp2 = 0; - if (curObject->index == 2) { - if (_vm->_characterList[2].sceneId == 77 || _vm->_characterList[2].sceneId == 86) - temp2 = 1; - else - temp2 = 0; - } else { - temp2 = 1; - } - - if (!temp2) - shapesIndex = -1; - } - - xpos = curObject->x1; - ypos = curObject->y1; - - int tempX = 0, tempY = 0; - if (curObject->flags & 0x1) { - tempX = (xOffsetTable1[curObject->index] * _brandonScaleX) >> 8; - tempY = yOffsetTable1[curObject->index]; - } else { - tempX = (xOffsetTable2[curObject->index] * _brandonScaleX) >> 8; - tempY = yOffsetTable2[curObject->index]; - } - tempY = (tempY * _brandonScaleY) >> 8; - xpos += tempX; - ypos += tempY; - - if (_vm->_scaleMode && _brandonScaleX != 256) - ++xpos; - - if (curObject->index == 0 && shapesIndex != -1) { - if (!(_vm->_brandonStatusBit & 2)) { - flagUnk3 = 0x100; - if ((flagUnk1 & 0x200) || (flagUnk2 & 0x4000)) - flagUnk3 = 0; - - int tempFlags = 0; - if (flagUnk3 & 0x100) { - tempFlags = curObject->flags & 1; - tempFlags |= 0x800 | flagUnk1 | 0x100; - } - - if (!(flagUnk3 & 0x100) && (flagUnk2 & 0x4000)) { - tempFlags = curObject->flags & 1; - tempFlags |= 0x900 | flagUnk1 | 0x4000; - _screen->drawShape(drawPage, _vm->_shapes[shapesIndex], xpos, ypos, 2, tempFlags | 4, _vm->_brandonPoisonFlagsGFX, int(1), int(_vm->_brandonInvFlag), drawLayer, _brandonScaleX, _brandonScaleY); - } else { - if (!(flagUnk2 & 0x4000)) { - tempFlags = curObject->flags & 1; - tempFlags |= 0x900 | flagUnk1; - } - - _screen->drawShape(drawPage, _vm->_shapes[shapesIndex], xpos, ypos, 2, tempFlags | 4, _vm->_brandonPoisonFlagsGFX, int(1), drawLayer, _brandonScaleX, _brandonScaleY); - } - } - } else { - if (shapesIndex != -1) { - int tempFlags = 0; - if (curObject->flags & 1) - tempFlags = 1; - _screen->drawShape(drawPage, _vm->_shapes[shapesIndex], xpos, ypos, 2, tempFlags | 0x800, drawLayer); - } - } - } - } - - xpos = curObject->x1; - ypos = curObject->y1; - - curObject->flags |= 0x800; - if (curObject->index == 0) { - flagUnk3 = 0x100; - - if (flagUnk1 & 0x200 || flagUnk2 & 0x4000) - flagUnk3 = 0; - - if (_vm->_brandonStatusBit & 2) - curObject->flags &= 0xFFFFFFFE; - - if (!_vm->_scaleMode) { - if (flagUnk3 & 0x100) - _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x100, (uint8 *)_vm->_brandonPoisonFlagsGFX, int(1), drawLayer); - else if (flagUnk2 & 0x4000) - _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x4000, int(_vm->_brandonInvFlag), drawLayer); - else - _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1, drawLayer); - } else { - if (flagUnk3 & 0x100) - _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x104, (uint8 *)_vm->_brandonPoisonFlagsGFX, int(1), drawLayer, _brandonScaleX, _brandonScaleY); - else if (flagUnk2 & 0x4000) - _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x4004, int(_vm->_brandonInvFlag), drawLayer, _brandonScaleX, _brandonScaleY); - else - _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x4, drawLayer, _brandonScaleX, _brandonScaleY); - } - } else { - if (curObject->index >= 16 && curObject->index <= 27) - _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | 4, drawLayer, (int)_vm->_scaleTable[curObject->drawY], (int)_vm->_scaleTable[curObject->drawY]); - else - _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags, drawLayer); - } - } - curObject = curObject->nextAnimObject; - } -} - -void Animator_LoK::copyChangedObjectsForward(int refreshFlag) { - for (AnimObject *curObject = _objectQueue; curObject; curObject = curObject->nextAnimObject) { - if (curObject->active) { - if (curObject->refreshFlag || refreshFlag) { - int xpos = 0, ypos = 0, width = 0, height = 0; - xpos = (curObject->x1 >> 3) - (curObject->width2 >> 3) - 1; - ypos = curObject->y1 - curObject->height2; - width = curObject->width + (curObject->width2 >> 3) + 2; - height = curObject->height + curObject->height2 * 2; - - if (xpos < 1) - xpos = 1; - else if (xpos > 39) - continue; - - if (xpos + width > 39) - width = 39 - xpos; - - if (ypos < 8) - ypos = 8; - else if (ypos > 136) - continue; - - if (ypos + height > 136) - height = 136 - ypos; - - _screen->copyRegion(xpos << 3, ypos, xpos << 3, ypos, width << 3, height, 2, 0); - curObject->refreshFlag = 0; - } - } - } - - _screen->updateScreen(); -} - -void Animator_LoK::updateAllObjectShapes() { - restoreAllObjectBackgrounds(); - preserveAnyChangedBackgrounds(); - prepDrawAllObjects(); - copyChangedObjectsForward(0); -} - -void Animator_LoK::animRemoveGameItem(int index) { - restoreAllObjectBackgrounds(); - - AnimObject *animObj = &_items[index]; - animObj->sceneAnimPtr = 0; - animObj->animFrameNumber = -1; - animObj->refreshFlag = 1; - animObj->bkgdChangeFlag = 1; - updateAllObjectShapes(); - animObj->active = 0; - - objectRemoveQueue(_objectQueue, animObj); -} - -void Animator_LoK::animAddGameItem(int index, uint16 sceneId) { - restoreAllObjectBackgrounds(); - assert(sceneId < _vm->_roomTableSize); - Room *currentRoom = &_vm->_roomTable[sceneId]; - AnimObject *animObj = &_items[index]; - animObj->active = 1; - animObj->refreshFlag = 1; - animObj->bkgdChangeFlag = 1; - animObj->drawY = currentRoom->itemsYPos[index]; - animObj->sceneAnimPtr = _vm->_shapes[216 + currentRoom->itemsTable[index]]; - animObj->animFrameNumber = -1; - animObj->x1 = currentRoom->itemsXPos[index]; - animObj->y1 = currentRoom->itemsYPos[index]; - animObj->x1 -= fetchAnimWidth(animObj->sceneAnimPtr, _vm->_scaleTable[animObj->drawY]) >> 1; - animObj->y1 -= fetchAnimHeight(animObj->sceneAnimPtr, _vm->_scaleTable[animObj->drawY]); - animObj->x2 = animObj->x1; - animObj->y2 = animObj->y1; - animObj->width2 = 0; - animObj->height2 = 0; - _objectQueue = objectQueue(_objectQueue, animObj); - preserveAnyChangedBackgrounds(); - animObj->refreshFlag = 1; - animObj->bkgdChangeFlag = 1; -} - -void Animator_LoK::animAddNPC(int character) { - restoreAllObjectBackgrounds(); - AnimObject *animObj = &_actors[character]; - const Character *ch = &_vm->_characterList[character]; - - animObj->active = 1; - animObj->refreshFlag = 1; - animObj->bkgdChangeFlag = 1; - animObj->drawY = ch->y1; - animObj->sceneAnimPtr = _vm->_shapes[ch->currentAnimFrame]; - animObj->x1 = animObj->x2 = ch->x1 + _vm->_defaultShapeTable[ch->currentAnimFrame - 7].xOffset; - animObj->y1 = animObj->y2 = ch->y1 + _vm->_defaultShapeTable[ch->currentAnimFrame - 7].yOffset; - - if (ch->facing >= 1 && ch->facing <= 3) - animObj->flags |= 1; - else if (ch->facing >= 5 && ch->facing <= 7) - animObj->flags &= 0xFFFFFFFE; - - _objectQueue = objectQueue(_objectQueue, animObj); - preserveAnyChangedBackgrounds(); - animObj->refreshFlag = 1; - animObj->bkgdChangeFlag = 1; -} - -Animator_LoK::AnimObject *Animator_LoK::objectRemoveQueue(AnimObject *queue, AnimObject *rem) { - AnimObject *cur = queue; - AnimObject *prev = queue; - - while (cur != rem && cur) { - AnimObject *temp = cur->nextAnimObject; - if (!temp) - break; - prev = cur; - cur = temp; - } - - if (cur == queue) { - if (!cur) - return 0; - return cur->nextAnimObject; - } - - if (!cur->nextAnimObject) { - if (cur == rem) { - if (!prev) - return 0; - else - prev->nextAnimObject = 0; - } - } else { - if (cur == rem) - prev->nextAnimObject = rem->nextAnimObject; - } - - return queue; -} - -Animator_LoK::AnimObject *Animator_LoK::objectAddHead(AnimObject *queue, AnimObject *head) { - head->nextAnimObject = queue; - return head; -} - -Animator_LoK::AnimObject *Animator_LoK::objectQueue(AnimObject *queue, AnimObject *add) { - if (!queue || add->drawY <= queue->drawY) { - add->nextAnimObject = queue; - return add; - } - AnimObject *cur = queue; - AnimObject *prev = queue; - while (add->drawY > cur->drawY) { - AnimObject *temp = cur->nextAnimObject; - if (!temp) - break; - prev = cur; - cur = temp; - } - - if (add->drawY <= cur->drawY) { - prev->nextAnimObject = add; - add->nextAnimObject = cur; - } else { - cur->nextAnimObject = add; - add->nextAnimObject = 0; - } - return queue; -} - -void Animator_LoK::addObjectToQueue(AnimObject *object) { - if (!_objectQueue) - _objectQueue = objectAddHead(0, object); - else - _objectQueue = objectQueue(_objectQueue, object); -} - -void Animator_LoK::refreshObject(AnimObject *object) { - _objectQueue = objectRemoveQueue(_objectQueue, object); - if (_objectQueue) - _objectQueue = objectQueue(_objectQueue, object); - else - _objectQueue = objectAddHead(0, object); -} - -void Animator_LoK::makeBrandonFaceMouse() { - Common::Point mouse = _vm->getMousePos(); - if (mouse.x >= _vm->_currentCharacter->x1) - _vm->_currentCharacter->facing = 3; - else - _vm->_currentCharacter->facing = 5; - animRefreshNPC(0); - updateAllObjectShapes(); -} - -int16 Animator_LoK::fetchAnimWidth(const uint8 *shape, int16 mult) { - if (_vm->gameFlags().useAltShapeHeader) - shape += 2; - return (((int16)READ_LE_UINT16((shape + 3))) * mult) >> 8; -} - -int16 Animator_LoK::fetchAnimHeight(const uint8 *shape, int16 mult) { - if (_vm->gameFlags().useAltShapeHeader) - shape += 2; - return (int16)(((int8)*(shape + 2)) * mult) >> 8; -} - -void Animator_LoK::setBrandonAnimSeqSize(int width, int height) { - restoreAllObjectBackgrounds(); - _brandonAnimSeqSizeWidth = _actors[0].width; - _brandonAnimSeqSizeHeight = _actors[0].height; - _actors[0].width = width + 1; - _actors[0].height = height; - preserveAllBackgrounds(); -} - -void Animator_LoK::resetBrandonAnimSeqSize() { - restoreAllObjectBackgrounds(); - _actors[0].width = _brandonAnimSeqSizeWidth; - _actors[0].height = _brandonAnimSeqSizeHeight; - preserveAllBackgrounds(); -} - -void Animator_LoK::animRefreshNPC(int character) { - AnimObject *animObj = &_actors[character]; - Character *ch = &_vm->characterList()[character]; - - animObj->refreshFlag = 1; - animObj->bkgdChangeFlag = 1; - int facing = ch->facing; - if (facing >= 1 && facing <= 3) - animObj->flags |= 1; - else if (facing >= 5 && facing <= 7) - animObj->flags &= 0xFFFFFFFE; - - animObj->drawY = ch->y1; - animObj->sceneAnimPtr = _vm->shapes()[ch->currentAnimFrame]; - animObj->animFrameNumber = ch->currentAnimFrame; - if (character == 0) { - if (_vm->brandonStatus() & 10) { - animObj->animFrameNumber = 88; - ch->currentAnimFrame = 88; - } - if (_vm->brandonStatus() & 2) { - animObj->animFrameNumber = _brandonDrawFrame; - ch->currentAnimFrame = _brandonDrawFrame; - animObj->sceneAnimPtr = _vm->shapes()[_brandonDrawFrame]; - if (_vm->_brandonStatusBit0x02Flag) { - ++_brandonDrawFrame; - // TODO: check this - if (_brandonDrawFrame >= 122) { - _brandonDrawFrame = 113; - _vm->_brandonStatusBit0x02Flag = 0; - } - } - } - } - - int xOffset = _vm->_defaultShapeTable[ch->currentAnimFrame - 7].xOffset; - int yOffset = _vm->_defaultShapeTable[ch->currentAnimFrame - 7].yOffset; - - if (_vm->_scaleMode) { - animObj->x1 = ch->x1; - animObj->y1 = ch->y1; - - int newScale = _vm->_scaleTable[ch->y1]; - _brandonScaleX = newScale; - _brandonScaleY = newScale; - - animObj->x1 += (_brandonScaleX * xOffset) >> 8; - animObj->y1 += (_brandonScaleY * yOffset) >> 8; - } else { - animObj->x1 = ch->x1 + xOffset; - animObj->y1 = ch->y1 + yOffset; - } - animObj->width2 = 4; - animObj->height2 = 3; - - refreshObject(animObj); -} - -void Animator_LoK::setCharacterDefaultFrame(int character) { - static const uint16 initFrameTable[] = { - 7, 41, 77, 0, 0 - }; - assert(character < ARRAYSIZE(initFrameTable)); - Character *edit = &_vm->characterList()[character]; - edit->sceneId = 0xFFFF; - edit->facing = 0; - edit->currentAnimFrame = initFrameTable[character]; - // edit->unk6 = 1; -} - -void Animator_LoK::setCharactersHeight() { - static const int8 initHeightTable[] = { - 48, 40, 48, 47, 56, - 44, 42, 47, 38, 35, - 40 - }; - for (int i = 0; i < 11; ++i) - _vm->characterList()[i].height = initHeightTable[i]; -} - -} // End of namespace Kyra diff --git a/engines/kyra/animator_lok.h b/engines/kyra/animator_lok.h deleted file mode 100644 index 55c4d571fd..0000000000 --- a/engines/kyra/animator_lok.h +++ /dev/null @@ -1,127 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef KYRA_ANIMATOR_LOK_H -#define KYRA_ANIMATOR_LOK_H - -namespace Kyra { -class KyraEngine_LoK; -class Screen; - -class Animator_LoK { -public: - struct AnimObject { - uint8 index; - uint32 active; - uint32 refreshFlag; - uint32 bkgdChangeFlag; - bool disable; - uint32 flags; - int16 drawY; - uint8 *sceneAnimPtr; - int16 animFrameNumber; - uint8 *background; - uint16 rectSize; - int16 x1, y1; - int16 x2, y2; - uint16 width; - uint16 height; - uint16 width2; - uint16 height2; - AnimObject *nextAnimObject; - }; - - Animator_LoK(KyraEngine_LoK *vm, OSystem *system); - virtual ~Animator_LoK(); - - operator bool() const { return _initOk; } - - void init(int actors, int items, int sprites); - void close(); - - AnimObject *objects() { return _screenObjects; } - AnimObject *actors() { return _actors; } - AnimObject *items() { return _items; } - AnimObject *sprites() { return _sprites; } - - void initAnimStateList(); - void preserveAllBackgrounds(); - void flagAllObjectsForBkgdChange(); - void flagAllObjectsForRefresh(); - void restoreAllObjectBackgrounds(); - void preserveAnyChangedBackgrounds(); - virtual void prepDrawAllObjects(); - void copyChangedObjectsForward(int refreshFlag); - - void updateAllObjectShapes(); - void animRemoveGameItem(int index); - void animAddGameItem(int index, uint16 sceneId); - void animAddNPC(int character); - void animRefreshNPC(int character); - - void clearQueue() { _objectQueue = 0; } - void addObjectToQueue(AnimObject *object); - void refreshObject(AnimObject *object); - - void makeBrandonFaceMouse(); - void setBrandonAnimSeqSize(int width, int height); - void resetBrandonAnimSeqSize(); - void setCharacterDefaultFrame(int character); - void setCharactersHeight(); - - int16 fetchAnimWidth(const uint8 *shape, int16 mult); - int16 fetchAnimHeight(const uint8 *shape, int16 mult); - - int _noDrawShapesFlag; - uint16 _brandonDrawFrame; - int _brandonScaleX; - int _brandonScaleY; - -protected: - KyraEngine_LoK *_vm; - Screen *_screen; - OSystem *_system; - bool _initOk; - - AnimObject *_screenObjects; - - AnimObject *_actors; - AnimObject *_items; - AnimObject *_sprites; - - uint8 *_actorBkgBackUp[2]; - - AnimObject *objectRemoveQueue(AnimObject *queue, AnimObject *rem); - AnimObject *objectAddHead(AnimObject *queue, AnimObject *head); - AnimObject *objectQueue(AnimObject *queue, AnimObject *add); - - void preserveOrRestoreBackground(AnimObject *obj, bool restore); - - AnimObject *_objectQueue; - - int _brandonAnimSeqSizeWidth; - int _brandonAnimSeqSizeHeight; -}; - -} // End of namespace Kyra - -#endif diff --git a/engines/kyra/animator_mr.cpp b/engines/kyra/animator_mr.cpp deleted file mode 100644 index 3b9454ce56..0000000000 --- a/engines/kyra/animator_mr.cpp +++ /dev/null @@ -1,456 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "kyra/kyra_mr.h" -#include "kyra/resource.h" -#include "kyra/wsamovie.h" - -#include "common/system.h" - -namespace Kyra { - -void KyraEngine_MR::restorePage3() { - screen()->copyBlockToPage(2, 0, 0, 320, 200, _gamePlayBuffer); -} - -void KyraEngine_MR::clearAnimObjects() { - for (int i = 0; i < 67; ++i) - _animObjects[i].enabled = false; - - _animObjects[0].index = 0; - _animObjects[0].type = 0; - _animObjects[0].enabled = true; - _animObjects[0].specialRefresh = 1; - _animObjects[0].flags = 0x800; - _animObjects[0].width = 57; - _animObjects[0].height = 91; - _animObjects[0].width2 = 4; - _animObjects[0].height2 = 10; - - for (int i = 1; i < 17; ++i) { - _animObjects[i].index = i; - _animObjects[i].type = 2; - _animObjects[i].flags = 0; - _animObjects[i].enabled = false; - _animObjects[i].needRefresh = 0; - _animObjects[i].specialRefresh = 1; - } - - for (int i = 17; i <= 66; ++i) { - _animObjects[i].index = i; - _animObjects[i].type = 1; - _animObjects[i].specialRefresh = 1; - _animObjects[i].flags = 0x800; - _animObjects[i].width = 24; - _animObjects[i].height = 20; - _animObjects[i].width2 = 0; - _animObjects[i].height2 = 0; - } -} - -void KyraEngine_MR::animSetupPaletteEntry(AnimObj *anim) { - int layer = _screen->getLayer(anim->xPos1, anim->yPos1) - 1; - int16 count = 0; - for (int i = 0; i < 3; ++i) - count += _sceneDatPalette[layer*3+i]; - count /= 3; - count *= -1; - count = MAX(0, MIN(count, 10)); - anim->palette = count / 3; -} - -void KyraEngine_MR::drawAnimObjects() { - for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { - if (!curObject->enabled) - continue; - - int x = curObject->xPos2 - (_screen->getScreenDim(2)->sx << 3); - int y = curObject->yPos2 - _screen->getScreenDim(2)->sy; - int layer = 7; - - if (curObject->flags & 0x800) { - if (!curObject->specialRefresh) - layer = 0; - else - layer = getDrawLayer(curObject->xPos1, curObject->yPos1); - } - - if (curObject->index) - drawSceneAnimObject(curObject, x, y, layer); - else - drawCharacterAnimObject(curObject, x, y, layer); - } -} - -void KyraEngine_MR::drawSceneAnimObject(AnimObj *obj, int x, int y, int layer) { - if (obj->type == 1) { - if (obj->shapeIndex1 == 0xFFFF) - return; - int scale = getScale(obj->xPos1, obj->yPos1); - _screen->drawShape(2, getShapePtr(obj->shapeIndex1), x, y, 2, obj->flags | 0x104, _paletteOverlay, obj->palette, layer, scale, scale); - } else { - if (obj->shapePtr) { - _screen->drawShape(2, obj->shapePtr, x, y, 2, obj->flags, 7); - } else { - if (obj->shapeIndex3 == 0xFFFF || obj->animNum == 0xFFFF) - return; - uint16 flags = 0x4000; - if (obj->flags & 0x800) - flags |= 0x8000; - x = obj->xPos2 - _sceneAnimMovie[obj->animNum]->xAdd(); - y = obj->yPos2 - _sceneAnimMovie[obj->animNum]->yAdd(); - _sceneAnimMovie[obj->animNum]->displayFrame(obj->shapeIndex3, 2, x, y, flags | layer, 0, 0); - } - } -} - -void KyraEngine_MR::drawCharacterAnimObject(AnimObj *obj, int x, int y, int layer) { - if (_drawNoShapeFlag) - return; - - if (_mainCharacter.animFrame < 9) - _mainCharacter.animFrame = 87; - - if (obj->shapeIndex1 == 0xFFFF || _mainCharacter.animFrame == 87) - return; - - _screen->drawShape(2, getShapePtr(421), _mainCharacter.x3, _mainCharacter.y3, 2, obj->flags | 0x304, _paletteOverlay, 3, layer, _charScale, _charScale); - uint8 *shape = getShapePtr(_mainCharacter.animFrame); - if (shape) - _screen->drawShape(2, shape, x, y, 2, obj->flags | 4, layer, _charScale, _charScale); -} - -void KyraEngine_MR::refreshAnimObjects(int force) { - for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { - if (!curObject->enabled) - continue; - if (!curObject->needRefresh && !force) - continue; - - const int scale = (curObject->index == 0) ? _charScale : 0; - - int x = curObject->xPos2 - curObject->width2; - if (scale) - x -= (0x100 - scale) >> 4; - - if (x < 0) - x = 0; - if (x >= 320) - x = 319; - - int y = curObject->yPos2 - curObject->height2; - if (scale) - y -= (0x100 - scale) >> 3; - if (y < 0) - y = 0; - if (y >= 187) - y = 186; - - int width = curObject->width + curObject->width2 + 8; - int height = curObject->height + curObject->height2*2; - if (width + x > 320) - width -= width + x - 322; - - const int maxY = _inventoryState ? 143 : 187; - if (height + y > maxY) - height -= height + y - (maxY + 1); - - if (height > 0) { - _screen->copyRegion(x, y, x, y, width, height, 2, 0, Screen::CR_NO_P_CHECK); - } - - curObject->needRefresh = false; - } -} - -void KyraEngine_MR::updateItemAnimations() { - bool nextFrame = false; - - if (_itemAnimDefinition[0].itemIndex == -1) - return; - - const ItemAnimDefinition *s = &_itemAnimDefinition[_nextAnimItem]; - ActiveItemAnim *a = &_activeItemAnim[_nextAnimItem]; - _nextAnimItem = (_nextAnimItem + 1) % 10; - - if (_system->getMillis() < a->nextFrameTime) - return; - - uint16 shpIdx = s->frames[a->currentFrame].index + 248; - if (s->itemIndex == _mouseState && s->itemIndex == _itemInHand && _screen->isMouseVisible()) { - nextFrame = true; - _screen->setMouseCursor(12, 19, getShapePtr(shpIdx)); - } - - if (_inventoryState) { - for (int i = 0; i < 10; i++) { - if (s->itemIndex == _mainCharacter.inventory[i]) { - nextFrame = true; - _screen->drawShape(2, getShapePtr(422 + i), 9, 0, 0, 0); - _screen->drawShape(2, getShapePtr(shpIdx), 9, 0, 0, 0); - _screen->copyRegion(9, 0, _inventoryX[i], _inventoryY[i], 24, 20, 2, 0, Screen::CR_NO_P_CHECK); - } - } - } - - _screen->updateScreen(); - - for (int i = 17; i < 66; i++) { - AnimObj *animObject = &_animObjects[i]; - if (animObject->shapeIndex2 == s->itemIndex + 248) { - animObject->shapePtr = getShapePtr(shpIdx); - animObject->shapeIndex1 = shpIdx; - animObject->needRefresh = true; - nextFrame = true; - } - } - - if (nextFrame) { - a->nextFrameTime = _system->getMillis() + (s->frames[a->currentFrame].delay * _tickLength); - a->currentFrame = (a->currentFrame + 1) % s->numFrames; - } -} - -void KyraEngine_MR::updateCharacterAnim(int charId) { - AnimObj *obj = &_animObjects[0]; - obj->needRefresh = true; - obj->flags &= ~1; - obj->xPos1 = _mainCharacter.x1; - obj->yPos1 = _mainCharacter.y1; - obj->shapePtr = getShapePtr(_mainCharacter.animFrame); - obj->shapeIndex1 = obj->shapeIndex2 = _mainCharacter.animFrame; - - int shapeOffsetX = 0, shapeOffsetY = 0; - if (_mainCharacter.animFrame >= 50 && _mainCharacter.animFrame <= 87) { - shapeOffsetX = _malcolmShapeXOffset; - shapeOffsetY = _malcolmShapeYOffset; - } else { - shapeOffsetX = _animShapeXAdd; - shapeOffsetY = _animShapeYAdd; - } - - obj->xPos2 = _mainCharacter.x1; - obj->yPos2 = _mainCharacter.y1; - _charScale = getScale(_mainCharacter.x1, _mainCharacter.y1); - obj->xPos2 += (shapeOffsetX * _charScale) >> 8; - obj->yPos2 += (shapeOffsetY * _charScale) >> 8; - _mainCharacter.x3 = _mainCharacter.x1 - (_charScale >> 4) - 1; - _mainCharacter.y3 = _mainCharacter.y1 - (_charScale >> 6) - 1; - if (_charBackUpWidth2 == -1) { - obj->width2 = 4; - obj->height2 = 10; - } - - for (int i = 1; i <= 16; ++i) { - if (_animObjects[i].enabled && _animObjects[i].specialRefresh) - _animObjects[i].needRefresh = true; - } - - _animList = deleteAnimListEntry(_animList, obj); - if (_animList) - _animList = addToAnimListSorted(_animList, obj); - else - _animList = initAnimList(_animList, obj); - - if (!_loadingState) - updateCharPal(1); -} - -void KyraEngine_MR::updateSceneAnim(int anim, int newFrame) { - AnimObj *animObject = &_animObjects[1+anim]; - if (!animObject->enabled) - return; - - animObject->needRefresh = true; - - if (_sceneAnims[anim].flags & 2) - animObject->flags |= 1; - else - animObject->flags &= ~1; - - if (_sceneAnims[anim].flags & 4) { - animObject->shapePtr = _sceneShapes[newFrame]; - animObject->shapeIndex2 = 0xFFFF; - animObject->shapeIndex3 = 0xFFFF; - animObject->animNum = 0xFFFF; - } else { - animObject->shapePtr = 0; - animObject->shapeIndex3 = newFrame; - animObject->animNum = anim; - } - - animObject->xPos1 = _sceneAnims[anim].x; - animObject->yPos1 = _sceneAnims[anim].y; - animObject->xPos2 = _sceneAnims[anim].x2; - animObject->yPos2 = _sceneAnims[anim].y2; - - if (_sceneAnims[anim].flags & 0x20) { - _animList = deleteAnimListEntry(_animList, animObject); - if (!_animList) - _animList = initAnimList(_animList, animObject); - else - _animList = addToAnimListSorted(_animList, animObject); - } -} - -void KyraEngine_MR::setupSceneAnimObject(int animId, uint16 flags, int x, int y, int x2, int y2, int w, - int h, int unk10, int specialSize, int unk14, int shape, const char *filename) { - restorePage3(); - SceneAnim &anim = _sceneAnims[animId]; - anim.flags = flags; - anim.x = x; - anim.y = y; - anim.x2 = x2; - anim.y2 = y2; - anim.width = w; - anim.height = h; - anim.specialSize = specialSize; - anim.shapeIndex = shape; - if (filename) - strcpy(anim.filename, filename); - - if (flags & 8) { - _sceneAnimMovie[animId]->open(filename, 1, 0); - if (_sceneAnimMovie[animId]->opened()) { - anim.wsaFlag = 1; - if (x2 == -1) - x2 = _sceneAnimMovie[animId]->xAdd(); - if (y2 == -1) - y2 = _sceneAnimMovie[animId]->yAdd(); - if (w == -1) - w = _sceneAnimMovie[animId]->width(); - if (h == -1) - h = _sceneAnimMovie[animId]->height(); - if (x == -1) - x = (w >> 1) + x2; - if (y == -1) - y = y2 + h - 1; - - anim.x = x; - anim.y = y; - anim.x2 = x2; - anim.y2 = y2; - anim.width = w; - anim.height = h; - } - } - - AnimObj *obj = &_animObjects[1+animId]; - obj->enabled = true; - obj->needRefresh = true; - - obj->specialRefresh = (anim.flags & 0x20) ? 1 : 0; - obj->flags = (anim.flags & 0x10) ? 0x800 : 0; - if (anim.flags & 2) - obj->flags |= 1; - - obj->xPos1 = anim.x; - obj->yPos1 = anim.y; - - if ((anim.flags & 4) && anim.shapeIndex != -1) - obj->shapePtr = _sceneShapes[anim.shapeIndex]; - else - obj->shapePtr = 0; - - if (anim.flags & 8) { - obj->shapeIndex3 = anim.shapeIndex; - obj->animNum = animId; - } else { - obj->shapeIndex3 = 0xFFFF; - obj->animNum = 0xFFFF; - } - - obj->xPos3 = obj->xPos2 = anim.x2; - obj->yPos3 = obj->yPos2 = anim.y2; - obj->width = anim.width; - obj->height = anim.height; - obj->width2 = obj->height2 = anim.specialSize; - - if (_animList) - _animList = addToAnimListSorted(_animList, obj); - else - _animList = initAnimList(_animList, obj); -} - -void KyraEngine_MR::removeSceneAnimObject(int anim, int refresh) { - AnimObj *obj = &_animObjects[anim+1]; - restorePage3(); - obj->shapeIndex3 = 0xFFFF; - obj->animNum = 0xFFFF; - obj->needRefresh = true; - - if (refresh) - refreshAnimObjectsIfNeed(); - - obj->enabled = false; - _animList = deleteAnimListEntry(_animList, obj); - _sceneAnimMovie[anim]->close(); -} - -void KyraEngine_MR::setCharacterAnimDim(int w, int h) { - restorePage3(); - _charBackUpWidth = _animObjects[0].width; - _charBackUpWidth2 = _animObjects[0].width2; - _charBackUpHeight = _animObjects[0].height; - _charBackUpHeight2 = _animObjects[0].height2; - - _animObjects[0].width2 = (w - _charBackUpWidth) / 2; - _animObjects[0].height2 = h - _charBackUpHeight; - _animObjects[0].width = w; - _animObjects[0].height = h; -} - -void KyraEngine_MR::resetCharacterAnimDim() { - restorePage3(); - _animObjects[0].width2 = _charBackUpWidth2; - _animObjects[0].height2 = _charBackUpHeight2; - _animObjects[0].width = _charBackUpWidth; - _animObjects[0].height = _charBackUpHeight; - _charBackUpWidth2 = _charBackUpHeight2 = -1; - _charBackUpWidth = _charBackUpHeight = -1; -} - -void KyraEngine_MR::showIdleAnim() { - if (_mainCharacter.sceneId == 20 || _mainCharacter.sceneId == 21 - || _mainCharacter.sceneId == 12 || _mainCharacter.sceneId == 11) - return; - - if (_mainCharacter.animFrame == 87) - return; - - if (!_nextIdleType && !talkObjectsInCurScene()) { - randomSceneChat(); - } else { - static const char *const facingTable[] = { - "A", "R", "R", "FR", "FX", "FL", "L", "L" - }; - - Common::String filename = Common::String::format( "MI0%s%.02d.EMC", facingTable[_mainCharacter.facing], _characterShapeFile); - - if (_res->exists(filename.c_str())) - runAnimationScript(filename.c_str(), 1, 1, 1, 1); - } - - _nextIdleType = !_nextIdleType; -} - -} // End of namespace Kyra diff --git a/engines/kyra/animator_tim.cpp b/engines/kyra/animator_tim.cpp deleted file mode 100644 index b1cfc6a6a8..0000000000 --- a/engines/kyra/animator_tim.cpp +++ /dev/null @@ -1,235 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "kyra/script_tim.h" -#include "kyra/wsamovie.h" -#include "kyra/screen_lol.h" - -#ifdef ENABLE_LOL -#include "kyra/lol.h" -#else -#include "kyra/screen_v2.h" -#endif - -#include "common/system.h" - -namespace Kyra { - -#ifdef ENABLE_LOL -TimAnimator::TimAnimator(LoLEngine *engine, Screen_v2 *screen_v2, OSystem *system, bool useParts) : _vm(engine), _screen(screen_v2), _system(system), _useParts(useParts) { -#else -TimAnimator::TimAnimator(KyraEngine_v1 *engine, Screen_v2 *screen_v2, OSystem *system, bool useParts) : _vm(engine), _screen(screen_v2), _system(system), _useParts(useParts) { -#endif - _animations = new Animation[TIM::kWSASlots]; - memset(_animations, 0, TIM::kWSASlots * sizeof(Animation)); - - if (_useParts) { - for (int i = 0; i < TIM::kWSASlots; i++) { - _animations[i].parts = new AnimPart[TIM::kAnimParts]; - memset(_animations[i].parts, 0, TIM::kAnimParts * sizeof(AnimPart)); - } - } -} - -TimAnimator::~TimAnimator() { - for (int i = 0; i < TIM::kWSASlots; i++) { - delete _animations[i].wsa; - if (_useParts) - delete[] _animations[i].parts; - } - - delete[] _animations; -} - -void TimAnimator::init(int animIndex, Movie *wsa, int x, int y, int wsaCopyParams, int frameDelay) { - Animation *anim = &_animations[animIndex]; - anim->wsa = wsa; - anim->x = x; - anim->y = y; - anim->wsaCopyParams = wsaCopyParams; - anim->frameDelay = frameDelay; - anim->enable = 0; - anim->lastPart = -1; -} - -void TimAnimator::reset(int animIndex, bool clearStruct) { - Animation *anim = &_animations[animIndex]; - if (!anim) - return; - anim->field_D = 0; - anim->enable = 0; - delete anim->wsa; - anim->wsa = 0; - - if (clearStruct) { - if (_useParts) - delete[] anim->parts; - - memset(anim, 0, sizeof(Animation)); - - if (_useParts) { - anim->parts = new AnimPart[TIM::kAnimParts]; - memset(anim->parts, 0, TIM::kAnimParts * sizeof(AnimPart)); - } - } -} - -void TimAnimator::displayFrame(int animIndex, int page, int frame, int flags) { - Animation *anim = &_animations[animIndex]; - if ((anim->wsaCopyParams & 0x4000) != 0) - page = 2; - // WORKAROUND for some bugged scripts that will try to display frames of non-existent animations - if (anim->wsa) - anim->wsa->displayFrame(frame, page, anim->x, anim->y, (flags == -1) ? (anim->wsaCopyParams & 0xF0FF) : flags, 0, 0); - if (!page) - _screen->updateScreen(); -} - -#ifdef ENABLE_LOL -void TimAnimator::setupPart(int animIndex, int part, int firstFrame, int lastFrame, int cycles, int nextPart, int partDelay, int f, int sfxIndex, int sfxFrame) { - AnimPart *a = &_animations[animIndex].parts[part]; - a->firstFrame = firstFrame; - a->lastFrame = lastFrame; - a->cycles = cycles; - a->nextPart = nextPart; - a->partDelay = partDelay; - a->field_A = f; - a->sfxIndex = sfxIndex; - a->sfxFrame = sfxFrame; -} - -void TimAnimator::start(int animIndex, int part) { - if (!_vm || !_system || !_screen) - return; - - Animation *anim = &_animations[animIndex]; - anim->curPart = part; - AnimPart *p = &anim->parts[part]; - anim->enable = 1; - anim->nextFrame = _system->getMillis() + anim->frameDelay * _vm->_tickLength; - anim->curFrame = p->firstFrame; - anim->cyclesCompleted = 0; - - // WORKAROUND for some bugged scripts that will try to display frames of non-existent animations - if (anim->wsa) - anim->wsa->displayFrame(anim->curFrame - 1, 0, anim->x, anim->y, 0, 0, 0); -} - -void TimAnimator::stop(int animIndex) { - Animation *anim = &_animations[animIndex]; - anim->enable = 0; - anim->field_D = 0; - if (animIndex == 5) { - delete anim->wsa; - anim->wsa = 0; - } -} - -void TimAnimator::update(int animIndex) { - if (!_vm || !_system || !_screen) - return; - - Animation *anim = &_animations[animIndex]; - if (!anim->enable || anim->nextFrame >= _system->getMillis()) - return; - - AnimPart *p = &anim->parts[anim->curPart]; - anim->nextFrame = 0; - - int step = 0; - if (p->lastFrame >= p->firstFrame) { - step = 1; - anim->curFrame++; - } else { - step = -1; - anim->curFrame--; - } - - if (anim->curFrame == (p->lastFrame + step)) { - anim->cyclesCompleted++; - - if ((anim->cyclesCompleted > p->cycles) || anim->field_D) { - anim->lastPart = anim->curPart; - - if ((p->nextPart == -1) || (anim->field_D && p->field_A)) { - anim->enable = 0; - anim->field_D = 0; - return; - } - - anim->nextFrame += (p->partDelay * _vm->_tickLength); - anim->curPart = p->nextPart; - - p = &anim->parts[anim->curPart]; - anim->curFrame = p->firstFrame; - anim->cyclesCompleted = 0; - - } else { - anim->curFrame = p->firstFrame; - } - } - - if (p->sfxIndex != -1 && p->sfxFrame == anim->curFrame) - _vm->snd_playSoundEffect(p->sfxIndex, -1); - - anim->nextFrame += (anim->frameDelay * _vm->_tickLength); - - anim->wsa->displayFrame(anim->curFrame - 1, 0, anim->x, anim->y, 0, 0, 0); - anim->nextFrame += _system->getMillis(); -} - -void TimAnimator::playPart(int animIndex, int firstFrame, int lastFrame, int delay) { - if (!_vm || !_system || !_screen) - return; - - Animation *anim = &_animations[animIndex]; - // WORKAROUND for some bugged scripts that will try to play invalid animations - if (!anim->wsa) - return; - - int step = (lastFrame >= firstFrame) ? 1 : -1; - for (int i = firstFrame; i != (lastFrame + step); i += step) { - uint32 next = _system->getMillis() + delay * _vm->_tickLength; - if (anim->wsaCopyParams & 0x4000) { - _screen->copyRegion(112, 0, 112, 0, 176, 120, 6, 2); - anim->wsa->displayFrame(i - 1, 2, anim->x, anim->y, anim->wsaCopyParams & 0x1000 ? 0x5000 : 0x4000, _vm->_transparencyTable1, _vm->_transparencyTable2); - _screen->copyRegion(112, 0, 112, 0, 176, 120, 2, 0); - _screen->updateScreen(); - } else { - anim->wsa->displayFrame(i - 1, 0, anim->x, anim->y, 0, 0, 0); - _screen->updateScreen(); - } - int32 del = (int32)(next - _system->getMillis()); - if (del > 0) - _vm->delay(del, true); - } -} - -int TimAnimator::resetLastPart(int animIndex) { - Animation *anim = &_animations[animIndex]; - int8 res = -1; - SWAP(res, anim->lastPart); - return res; -} -#endif - -} // End of namespace Kyra diff --git a/engines/kyra/animator_v2.cpp b/engines/kyra/animator_v2.cpp deleted file mode 100644 index 5ac154bdce..0000000000 --- a/engines/kyra/animator_v2.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "kyra/kyra_v2.h" -#include "kyra/screen_v2.h" - -namespace Kyra { - -void KyraEngine_v2::allocAnimObjects(int actors, int anims, int items) { - _animObjects = new AnimObj[actors + anims + items]; - assert(_animObjects); - - memset(_animObjects, 0, sizeof(AnimObj) * (actors + anims + items)); - - _animActor = _animObjects; - _animAnims = _animObjects + actors; - _animItems = _animObjects + actors + anims; -} - -KyraEngine_v2::AnimObj *KyraEngine_v2::initAnimList(AnimObj *list, AnimObj *entry) { - entry->nextObject = list; - return entry; -} - -KyraEngine_v2::AnimObj *KyraEngine_v2::addToAnimListSorted(AnimObj *list, AnimObj *add) { - add->nextObject = 0; - - if (!list) - return add; - - if (add->yPos1 <= list->yPos1) { - add->nextObject = list; - return add; - } - - AnimObj *cur = list; - AnimObj *prev = list; - while (add->yPos1 > cur->yPos1) { - AnimObj *temp = cur->nextObject; - if (!temp) - break; - prev = cur; - cur = temp; - } - - if (add->yPos1 <= cur->yPos1) { - prev->nextObject = add; - add->nextObject = cur; - } else { - cur->nextObject = add; - add->nextObject = 0; - } - return list; -} - -KyraEngine_v2::AnimObj *KyraEngine_v2::deleteAnimListEntry(AnimObj *list, AnimObj *entry) { - if (!list) - return 0; - - AnimObj *old = 0; - AnimObj *cur = list; - - while (true) { - if (cur == entry) - break; - if (!cur->nextObject) - break; - old = cur; - cur = cur->nextObject; - } - - if (cur != entry) - return list; - - if (cur == list) { - if (!cur->nextObject) - return 0; - cur = cur->nextObject; - return cur; - } - - if (!cur->nextObject) { - if (!old) - return 0; - old->nextObject = 0; - return list; - } - - if (cur != entry) - return list; - - old->nextObject = entry->nextObject; - return list; -} - -void KyraEngine_v2::refreshAnimObjectsIfNeed() { - for (AnimObj *curEntry = _animList; curEntry; curEntry = curEntry->nextObject) { - if (curEntry->enabled && curEntry->needRefresh) { - restorePage3(); - drawAnimObjects(); - refreshAnimObjects(0); - screen()->updateScreen(); - return; - } - } -} - -void KyraEngine_v2::flagAnimObjsForRefresh() { - for (AnimObj *curEntry = _animList; curEntry; curEntry = curEntry->nextObject) - curEntry->needRefresh = 1; -} - -void KyraEngine_v2::flagAnimObjsSpecialRefresh() { - for (AnimObj *curEntry = _animList; curEntry; curEntry = curEntry->nextObject) - curEntry->specialRefresh = 1; -} - -void KyraEngine_v2::addItemToAnimList(int item) { - assert(item >= 0 && item < _itemListSize); - - restorePage3(); - - AnimObj *animObj = _animItems + item; - - animObj->enabled = 1; - animObj->needRefresh = 1; - - int itemId = _itemList[item].id; - - animObj->xPos2 = animObj->xPos1 = _itemList[item].x; - animObj->yPos2 = animObj->yPos1 = _itemList[item].y; - - animObj->shapePtr = getShapePtr(itemId + _desc.itemShapeStart); - animSetupPaletteEntry(animObj); - animObj->shapeIndex2 = animObj->shapeIndex1 = itemId + _desc.itemShapeStart; - - int scaleY, scaleX; - scaleY = scaleX = getScale(animObj->xPos1, animObj->yPos1); - - uint8 *shapePtr = getShapePtr(itemId + _desc.itemShapeStart); - animObj->xPos3 = (animObj->xPos2 -= (screen_v2()->getShapeScaledWidth(shapePtr, scaleX) >> 1)); - animObj->yPos3 = (animObj->yPos2 -= screen_v2()->getShapeScaledHeight(shapePtr, scaleY)); - - animObj->width2 = animObj->height2 = 0; - - _animList = addToAnimListSorted(_animList, animObj); - animObj->needRefresh = 1; -} - -void KyraEngine_v2::deleteItemAnimEntry(int item) { - assert(item < _itemListSize); - - AnimObj *animObj = _animItems + item; - - restorePage3(); - - animObj->shapePtr = 0; - animObj->shapeIndex1 = 0xFFFF; - animObj->shapeIndex2 = 0xFFFF; - animObj->needRefresh = 1; - - refreshAnimObjectsIfNeed(); - - animObj->enabled = 0; - _animList = deleteAnimListEntry(_animList, animObj); -} - -} // End of namespace Kyra diff --git a/engines/kyra/chargen.cpp b/engines/kyra/chargen.cpp deleted file mode 100644 index 4724770782..0000000000 --- a/engines/kyra/chargen.cpp +++ /dev/null @@ -1,1982 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifdef ENABLE_EOB - -#include "kyra/eobcommon.h" -#include "kyra/resource.h" -#include "kyra/sound_intern.h" - -#include "common/savefile.h" -#include "common/str-array.h" - -#include "common/config-manager.h" -#include "base/plugins.h" -#include "engines/metaengine.h" -#include "engines/game.h" - -namespace Kyra { - -// Character Generator - -class CharacterGenerator { -public: - CharacterGenerator(EoBCoreEngine *vm, Screen_EoB *screen); - ~CharacterGenerator(); - - bool start(EoBCharacter *characters, uint8 ***faceShapes); - -private: - void init(); - void initButtonsFromList(int first, int numButtons); - void initButton(int index, int x, int y, int w, int h, int keyCode); - void checkForCompleteParty(); - void toggleSpecialButton(int index, int bodyCustom, int pageNum); - void processSpecialButton(int index); - int viewDeleteCharacter(); - void createPartyMember(); - int raceSexMenu(); - int classMenu(int raceSex); - int alignmentMenu(int cClass); - int getInput(Button *buttonList); - void updateMagicShapes(); - void generateStats(int index); - void modifyMenu(); - void statsAndFacesMenu(); - void faceSelectMenu(); - int getNextFreeFaceShape(int shpIndex, int charSex, int step, int8 *selectedPortraits); - void processFaceMenuSelection(int index); - void printStats(int index, int mode); - void processNameInput(int index, int textColor); - int rollDice(); - int modifyStat(int index, int8 *stat1, int8 *stat2); - int getMaxHp(int cclass, int constitution, int level1, int level2, int level3); - int getMinHp(int cclass, int constitution, int level1, int level2, int level3); - void finish(); - - uint8 **_chargenMagicShapes; - uint8 *_chargenButtonLabels[17]; - int _activeBox; - int _magicShapesBox; - int _updateBoxShapesIndex; - int _lastUpdateBoxShapesIndex; - uint32 _chargenMagicShapeTimer; - int8 _chargenSelectedPortraits[4]; - int8 _chargenSelectedPortraits2[4]; - - uint16 _chargenMinStats[7]; - uint16 _chargenMaxStats[7]; - - const char *const *_chargenStrings1; - const char *const *_chargenStrings2; - const char *const *_chargenStatStrings; - const char *const *_chargenRaceSexStrings; - const char *const *_chargenClassStrings; - const char *const *_chargenAlignmentStrings; - const char *const *_chargenEnterGameStrings; - - const uint8 *_chargenStartLevels; - const uint8 *_chargenClassMinStats; - const uint8 *_chargenRaceMinStats; - const uint16 *_chargenRaceMaxStats; - - const EoBChargenButtonDef *_chargenButtonDefs; - - static const EoBChargenButtonDef _chargenButtonDefsDOS[]; - static const uint16 _chargenButtonKeyCodesFMTOWNS[]; - static const CreatePartyModButton _chargenModButtons[]; - static const EoBRect8 _chargenButtonBodyCoords[]; - static const int16 _chargenBoxX[]; - static const int16 _chargenBoxY[]; - static const int16 _chargenNameFieldX[]; - static const int16 _chargenNameFieldY[]; - - static const int32 _classMenuMasks[]; - static const int32 _alignmentMenuMasks[]; - - static const int16 _raceModifiers[]; - - EoBCharacter *_characters; - uint8 **_faceShapes; - - EoBCoreEngine *_vm; - Screen_EoB *_screen; -}; - -CharacterGenerator::CharacterGenerator(EoBCoreEngine *vm, Screen_EoB *screen) : _vm(vm), _screen(screen), - _characters(0), _faceShapes(0), _chargenMagicShapes(0), _chargenMagicShapeTimer(0), - _updateBoxShapesIndex(0), _lastUpdateBoxShapesIndex(0), _magicShapesBox(6), _activeBox(0) { - - _chargenStatStrings = _vm->_chargenStatStrings; - _chargenRaceSexStrings = _vm->_chargenRaceSexStrings; - _chargenClassStrings = _vm->_chargenClassStrings; - _chargenAlignmentStrings = _vm->_chargenAlignmentStrings; - - memset(_chargenSelectedPortraits, -1, sizeof(_chargenSelectedPortraits)); - memset(_chargenSelectedPortraits2, 0, sizeof(_chargenSelectedPortraits2)); - memset(_chargenMinStats, 0, sizeof(_chargenMinStats)); - memset(_chargenMaxStats, 0, sizeof(_chargenMaxStats)); - - int temp; - _chargenStrings1 = _vm->staticres()->loadStrings(kEoBBaseChargenStrings1, temp); - _chargenStrings2 = _vm->staticres()->loadStrings(kEoBBaseChargenStrings2, temp); - _chargenStartLevels = _vm->staticres()->loadRawData(kEoBBaseChargenStartLevels, temp); - _chargenEnterGameStrings = _vm->staticres()->loadStrings(kEoBBaseChargenEnterGameStrings, temp); - _chargenClassMinStats = _vm->staticres()->loadRawData(kEoBBaseChargenClassMinStats, temp); - _chargenRaceMinStats = _vm->staticres()->loadRawData(kEoBBaseChargenRaceMinStats, temp); - _chargenRaceMaxStats = _vm->staticres()->loadRawDataBe16(kEoBBaseChargenRaceMaxStats, temp); - - EoBChargenButtonDef *chargenButtonDefs = new EoBChargenButtonDef[41]; - memcpy(chargenButtonDefs, _chargenButtonDefsDOS, 41 * sizeof(EoBChargenButtonDef)); - - if (_vm->gameFlags().platform == Common::kPlatformFMTowns) { - const uint16 *c = _chargenButtonKeyCodesFMTOWNS; - for (int i = 0; i < 41; ++i) { - if (chargenButtonDefs[i].keyCode) - chargenButtonDefs[i].keyCode = *c++; - } - } - - _chargenButtonDefs = chargenButtonDefs; -} - -CharacterGenerator::~CharacterGenerator() { - if (_chargenMagicShapes) { - for (int i = 0; i < 10; i++) - delete[] _chargenMagicShapes[i]; - delete[] _chargenMagicShapes; - } - - for (int i = 0; i < 17; i++) - delete[] _chargenButtonLabels[i]; - - delete[] _chargenButtonDefs; - - _screen->clearPage(2); -} - -bool CharacterGenerator::start(EoBCharacter *characters, uint8 ***faceShapes) { - if (!characters || !faceShapes) { - warning("CharacterGenerator::start: Called without character data"); - return true; - } - - _characters = characters; - _faceShapes = *faceShapes; - - _vm->snd_stopSound(); - _vm->delay(_vm->_tickLength); - - init(); - - _screen->setScreenDim(2); - - checkForCompleteParty(); - initButtonsFromList(0, 5); - - _vm->snd_playSong(_vm->game() == GI_EOB1 ? 20 : 13); - _activeBox = 0; - - for (bool loop = true; loop && (!_vm->shouldQuit());) { - _vm->_gui->updateBoxFrameHighLight(_activeBox + 6); - int inputFlag = getInput(_vm->_activeButtons); - _vm->removeInputTop(); - - if (inputFlag) { - if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT]) { - _activeBox ^= 1; - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP] || inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN]) { - _activeBox ^= 2; - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE]) { - // Unlike the original we allow returning to the main menu - _vm->snd_stopSound(); - return false; - } - _vm->_gui->updateBoxFrameHighLight(-1); - } - - if (inputFlag & 0x8000) { - inputFlag = (inputFlag & 0x0F) - 1; - if (inputFlag == 4) { - loop = false; - } else { - _activeBox = inputFlag; - inputFlag = _vm->_keyMap[Common::KEYCODE_RETURN]; - } - } - - if (inputFlag == _vm->_keyMap[Common::KEYCODE_RETURN] || inputFlag == _vm->_keyMap[Common::KEYCODE_SPACE] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP5]) { - _vm->_gui->updateBoxFrameHighLight(-1); - if (_characters[_activeBox].name[0]) { - int b = _activeBox; - if (viewDeleteCharacter()) - loop = false; - if (b != _activeBox && !_characters[_activeBox].name[0]) - createPartyMember(); - } else { - createPartyMember(); - } - - initButtonsFromList(0, 5); - checkForCompleteParty(); - } - - if (loop == false) { - for (int i = 0; i < 4; i++) { - if (!_characters[i].name[0]) - loop = true; - } - } - } - - if (!_vm->shouldQuit()) { - processSpecialButton(15); - finish(); - } - - if (_vm->game() == GI_EOB2) - _vm->snd_fadeOut(); - - *faceShapes = _faceShapes; - return true; -} - -void CharacterGenerator::init() { - /*_screen->loadEoBBitmap("MENU", 0, 3, 3, 2); - Common::SeekableReadStream *s = _res->createReadStream("facedat.dmp"); - _screen->loadFileDataToPage(s, 2, 64000);*/ - - _screen->loadShapeSetBitmap("CHARGENA", 3, 3); - if (_faceShapes) { - for (int i = 0; i < 44; i++) - delete[] _faceShapes[i]; - delete[] _faceShapes; - } - - _faceShapes = new uint8*[44]; - for (int i = 0; i < 44; i++) - _faceShapes[i] = _screen->encodeShape((i % 10) << 2, (i / 10) << 5, 4, 32, true, _vm->_cgaMappingDefault); - _screen->_curPage = 0; - - _screen->loadEoBBitmap("CHARGEN", _vm->_cgaMappingDefault, 3, 3, 0); - _screen->loadShapeSetBitmap("CHARGENB", 3, 3); - if (_chargenMagicShapes) { - for (int i = 0; i < 10; i++) - delete[] _chargenMagicShapes[i]; - delete[] _chargenMagicShapes; - } - - _chargenMagicShapes = new uint8*[10]; - for (int i = 0; i < 10; i++) - _chargenMagicShapes[i] = _screen->encodeShape(i << 2, 0, 4, 32, true, _vm->_cgaMappingDefault); - - for (int i = 0; i < 17; i++) { - const CreatePartyModButton *c = &_chargenModButtons[i]; - _chargenButtonLabels[i] = c->labelW ? _screen->encodeShape(c->encodeLabelX, c->encodeLabelY, c->labelW, c->labelH, true, _vm->_cgaMappingDefault) : 0; - } - - _screen->convertPage(3, 2, _vm->_cgaMappingDefault); - _screen->_curPage = 0; - _screen->convertToHiColor(2); - _screen->shadeRect(142, 63, 306, 193, 4); - _screen->copyRegion(144, 64, 0, 0, 180, 128, 0, 2, Screen::CR_NO_P_CHECK); - _screen->updateScreen(); -} - -void CharacterGenerator::initButtonsFromList(int first, int numButtons) { - _vm->gui_resetButtonList(); - - for (int i = 0; i < numButtons; i++) { - const EoBChargenButtonDef *e = &_chargenButtonDefs[first + i]; - initButton(i, e->x, e->y, e->w, e->h, e->keyCode); - } - - _vm->gui_notifyButtonListChanged(); -} - -void CharacterGenerator::initButton(int index, int x, int y, int w, int h, int keyCode) { - Button *b = 0; - int cnt = 1; - - if (_vm->_activeButtons) { - Button *n = _vm->_activeButtons; - while (n->nextButton) { - ++cnt; - n = n->nextButton; - } - - ++cnt; - b = n->nextButton = &_vm->_activeButtonData[cnt]; - } else { - b = &_vm->_activeButtonData[0]; - _vm->_activeButtons = b; - } - - *b = Button(); - b->flags = 0x1100; - b->data0Val2 = 12; - b->data1Val2 = b->data2Val2 = 15; - b->data3Val2 = 8; - - b->index = index + 1; - b->x = x << 3; - b->y = y; - b->width = w; - b->height = h; - b->keyCode = keyCode; - b->keyCode2 = keyCode | 0x100; -} - -void CharacterGenerator::checkForCompleteParty() { - _screen->copyRegion(0, 0, 160, 0, 160, 128, 2, 2, Screen::CR_NO_P_CHECK); - int cp = _screen->setCurPage(2); - int x = (_vm->gameFlags().platform == Common::kPlatformFMTowns) ? 184 : 168; - _screen->printShadedText(_chargenStrings1[8], x, 16, 15, 0); - _screen->setCurPage(cp); - _screen->copyRegion(160, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); - - int numChars = 0; - for (int i = 0; i < 4; i++) { - if (_characters[i].name[0]) - numChars++; - } - - if (numChars == 4) { - _screen->setCurPage(2); - _screen->printShadedText(_chargenStrings1[0], x, 61, 15, 0); - _screen->setCurPage(0); - _screen->copyRegion(168, 61, 152, 125, 136, 40, 2, 0, Screen::CR_NO_P_CHECK); - toggleSpecialButton(15, 0, 0); - } else { - toggleSpecialButton(14, 0, 0); - } - - _screen->updateScreen(); -} - -void CharacterGenerator::toggleSpecialButton(int index, int bodyCustom, int pageNum) { - if (index >= 17) - return; - - const CreatePartyModButton *c = &_chargenModButtons[index]; - const EoBRect8 *p = &_chargenButtonBodyCoords[c->bodyIndex + bodyCustom]; - - int x2 = 20; - int y2 = 0; - - if (pageNum) { - x2 = c->destX + 2; - y2 = c->destY - 64; - } - - _screen->copyRegion(p->x << 3, p->y, x2 << 3, y2, p->w << 3, p->h, 2, 2, Screen::CR_NO_P_CHECK); - if (c->labelW) - _screen->drawShape(2, _chargenButtonLabels[index], (x2 << 3) + c->labelX, y2 + c->labelY, 0); - - if (pageNum == 2) - return; - - _screen->copyRegion(160, 0, c->destX << 3, c->destY, p->w << 3, p->h, 2, 0, Screen::CR_NO_P_CHECK); - _screen->updateScreen(); -} - -void CharacterGenerator::processSpecialButton(int index) { - toggleSpecialButton(index, 1, 0); - _vm->snd_playSoundEffect(76); - _vm->_system->delayMillis(80); - toggleSpecialButton(index, 0, 0); -} - -int CharacterGenerator::viewDeleteCharacter() { - initButtonsFromList(0, 7); - _vm->removeInputTop(); - - _vm->_gui->updateBoxFrameHighLight(-1); - printStats(_activeBox, 2); - - int res = 0; - for (bool loop = true; loop && _characters[_activeBox].name[0] && !_vm->shouldQuit();) { - _vm->_gui->updateBoxFrameHighLight(_activeBox + 6); - int inputFlag = getInput(_vm->_activeButtons); - int cbx = _activeBox; - _vm->removeInputTop(); - - if (inputFlag == _vm->_keyMap[Common::KEYCODE_RETURN] || inputFlag == _vm->_keyMap[Common::KEYCODE_SPACE] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP5] || inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE]) { - processSpecialButton(9); - res = 0; - loop = false; - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT]) { - cbx ^= 1; - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP] || inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN]) { - cbx ^= 2; - } - - if (inputFlag & 0x8000) { - inputFlag = (inputFlag & 0x0F) - 1; - if (inputFlag == 4) { - res = 1; - loop = false; - } else if (inputFlag == 5) { - processSpecialButton(9); - res = 0; - loop = false; - } else if (inputFlag == 6) { - if (_characters[_activeBox].name[0]) { - processSpecialButton(16); - _characters[_activeBox].name[0] = 0; - processNameInput(_activeBox, 12); - processFaceMenuSelection(_activeBox + 50); - } - } else { - cbx = inputFlag; - } - } - - if (loop == false) - _vm->_gui->updateBoxFrameHighLight(-1); - - if (!_characters[cbx].name[0]) - loop = false; - - if (cbx != _activeBox) { - _activeBox = cbx; - _vm->_gui->updateBoxFrameHighLight(-1); - if (loop) - printStats(_activeBox, 2); - } - } - - return res; -} - -void CharacterGenerator::createPartyMember() { - _screen->setScreenDim(2); - assert(_vm->_gui); - - for (int i = 0; i != 3 && !_vm->shouldQuit(); i++) { - bool bck = false; - - switch (i) { - case 0: - _characters[_activeBox].raceSex = raceSexMenu(); - break; - case 1: - _characters[_activeBox].cClass = classMenu(_characters[_activeBox].raceSex); - if (_characters[_activeBox].cClass == _vm->_keyMap[Common::KEYCODE_ESCAPE]) - bck = true; - break; - case 2: - _characters[_activeBox].alignment = alignmentMenu(_characters[_activeBox].cClass); - if (_characters[_activeBox].alignment == _vm->_keyMap[Common::KEYCODE_ESCAPE]) - bck = true; - break; - default: - break; - } - - if (bck) - i -= 2; - }; - - if (!_vm->shouldQuit()) { - generateStats(_activeBox); - statsAndFacesMenu(); - - for (_characters[_activeBox].name[0] = 0; _characters[_activeBox].name[0] == 0 && !_vm->shouldQuit();) { - processFaceMenuSelection(_chargenMinStats[6]); - printStats(_activeBox, 0); - _screen->printShadedText(_chargenStrings2[11], 149, 100, 9, 0); - if (!_vm->shouldQuit()) { - _vm->_gui->getTextInput(_characters[_activeBox].name, 24, 100, 10, 15, 0, 8); - processNameInput(_activeBox, 2); - } - } - } -} - -int CharacterGenerator::raceSexMenu() { - _screen->drawBox(_chargenBoxX[_activeBox], _chargenBoxY[_activeBox], _chargenBoxX[_activeBox] + 32, _chargenBoxY[_activeBox] + 33, 12); - _screen->copyRegion(0, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); - _screen->printShadedText(_chargenStrings2[8], 147, 67, 9, 0); - _vm->removeInputTop(); - - _vm->_gui->simpleMenu_setup(1, 0, _chargenRaceSexStrings, -1, 0, 0); - int16 res = -1; - - while (res == -1 && !_vm->shouldQuit()) { - res = _vm->_gui->simpleMenu_process(1, _chargenRaceSexStrings, 0, -1, 0); - updateMagicShapes(); - } - - return res; -} - -int CharacterGenerator::classMenu(int raceSex) { - int32 itemsMask = -1; - - for (int i = 0; i < 4; i++) { - // check for evil characters - if (_characters[i].name[0] && _characters[i].alignment > 5) - itemsMask = 0xFFFB; - } - - _vm->removeInputTop(); - updateMagicShapes(); - - _screen->copyRegion(0, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); - _screen->printShadedText(_chargenStrings2[9], 147, 67, 9, 0); - - toggleSpecialButton(5, 0, 0); - - itemsMask &= _classMenuMasks[raceSex / 2]; - _vm->_gui->simpleMenu_setup(2, 15, _chargenClassStrings, itemsMask, 0, 0); - - _vm->_mouseX = _vm->_mouseY = 0; - int16 res = -1; - - while (res == -1 && !_vm->shouldQuit()) { - updateMagicShapes(); - int in = getInput(0) & 0xFF; - Common::Point mp = _vm->getMousePos(); - - if (in == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_b]) { - res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; - } else if (_vm->posWithinRect(mp.x, mp.y, 264, 171, 303, 187)) { - if (in == 199 || in == 201) - res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; - else - _vm->removeInputTop(); - } else { - res = _vm->_gui->simpleMenu_process(2, _chargenClassStrings, 0, itemsMask, 0); - } - } - - _vm->removeInputTop(); - - if (res == _vm->_keyMap[Common::KEYCODE_ESCAPE]) - processSpecialButton(5); - - return res; -} - -int CharacterGenerator::alignmentMenu(int cClass) { - int32 itemsMask = -1; - - for (int i = 0; i < 4; i++) { - // check for paladins - if (_characters[i].name[0] && _characters[i].cClass == 2) - itemsMask = 0xFE3F; - } - - _vm->removeInputTop(); - updateMagicShapes(); - - _screen->copyRegion(0, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); - _screen->printShadedText(_chargenStrings2[10], 147, 67, 9, 0); - - toggleSpecialButton(5, 0, 0); - - itemsMask &= _alignmentMenuMasks[cClass]; - _vm->_gui->simpleMenu_setup(3, 9, _chargenAlignmentStrings, itemsMask, 0, 0); - - _vm->_mouseX = _vm->_mouseY = 0; - int16 res = -1; - - while (res == -1 && !_vm->shouldQuit()) { - updateMagicShapes(); - int in = getInput(0) & 0xFF; - Common::Point mp = _vm->getMousePos(); - - if (in == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_b]) { - res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; - } else if (_vm->posWithinRect(mp.x, mp.y, 264, 171, 303, 187)) { - if (in == 199 || in == 201) - res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; - else - _vm->removeInputTop(); - } else { - res = _vm->_gui->simpleMenu_process(3, _chargenAlignmentStrings, 0, itemsMask, 0); - } - } - - _vm->removeInputTop(); - - if (res == _vm->_keyMap[Common::KEYCODE_ESCAPE]) - processSpecialButton(5); - - return res; -} - -int CharacterGenerator::getInput(Button *buttonList) { - if (_vm->game() == GI_EOB1 && _vm->sound()->checkTrigger()) { - _vm->sound()->resetTrigger(); - _vm->snd_playSong(20); - } else if (_vm->game() == GI_EOB2 && !_vm->sound()->isPlaying()) { - // WORKAROUND for EOB II: The original implements the same sound trigger check as in EOB I. - // However, Westwood seems to have forgotten to set the trigger at the end of the AdLib song, - // so that the music will not loop. We simply check whether the sound driver is still playing. - _vm->delay(3 * _vm->_tickLength); - _vm->snd_playSong(13); - } - return _vm->checkInput(buttonList, false, 0); -} - -void CharacterGenerator::updateMagicShapes() { - if (_magicShapesBox != _activeBox) { - _chargenMagicShapeTimer = 0; - _magicShapesBox = _activeBox; - } - - if (_chargenMagicShapeTimer < _vm->_system->getMillis()) { - if (++_updateBoxShapesIndex > 9) - _updateBoxShapesIndex = 0; - _chargenMagicShapeTimer = _vm->_system->getMillis() + 2 * _vm->_tickLength; - } - - if (_updateBoxShapesIndex == _lastUpdateBoxShapesIndex) - return; - - _screen->copyRegion(_activeBox << 5, 128, 288, 128, 32, 32, 2, 2, Screen::CR_NO_P_CHECK); - _screen->drawShape(2, _chargenMagicShapes[_updateBoxShapesIndex], 288, 128, 0); - _screen->copyRegion(288, 128, _chargenBoxX[_activeBox], _chargenBoxY[_activeBox] + 1, 32, 32, 2, 0, Screen::CR_NO_P_CHECK); - _screen->updateScreen(); - - _lastUpdateBoxShapesIndex = _updateBoxShapesIndex; -} - -void CharacterGenerator::generateStats(int index) { - EoBCharacter *c = &_characters[index]; - - for (int i = 0; i < 3; i++) { - c->level[i] = _chargenStartLevels[(c->cClass << 2) + i]; - c->experience[i] = (_vm->game() == GI_EOB2 ? 69000 : 5000) / _chargenStartLevels[(c->cClass << 2) + 3]; - } - - int rc = c->raceSex >> 1; - for (int i = 0; i < 6; i++) { - _chargenMinStats[i] = MAX(_chargenClassMinStats[c->cClass * 6 + i], _chargenRaceMinStats[rc * 6 + i]); - _chargenMaxStats[i] = _chargenRaceMaxStats[rc * 6 + i]; - } - - if (_vm->_charClassModifier[c->cClass]) - _chargenMaxStats[0] = 18; - - uint16 sv[6]; - for (int i = 0; i < 6; i++) { - sv[i] = MAX(rollDice() + _raceModifiers[rc * 6 + i], _chargenMinStats[i]); - if (!i && sv[i] == 18) - sv[i] |= (uint16)(_vm->rollDice(1, 100) << 8); - if (sv[i] > _chargenMaxStats[i]) - sv[i] = _chargenMaxStats[i]; - } - - c->strengthCur = c->strengthMax = sv[0] & 0xFF; - c->strengthExtCur = c->strengthExtMax = sv[0] >> 8; - c->intelligenceCur = c->intelligenceMax = sv[1] & 0xFF; - c->wisdomCur = c->wisdomMax = sv[2] & 0xFF; - c->dexterityCur = c->dexterityMax = sv[3] & 0xFF; - c->constitutionCur = c->constitutionMax = sv[4] & 0xFF; - c->charismaCur = c->charismaMax = sv[5] & 0xFF; - c->armorClass = 10 + _vm->getDexterityArmorClassModifier(sv[3] & 0xFF); - c->hitPointsCur = 0; - - for (int l = 0; l < 3; l++) { - for (int i = 0; i < c->level[l]; i++) - c->hitPointsCur += _vm->generateCharacterHitpointsByLevel(index, 1 << l); - } - - c->hitPointsMax = c->hitPointsCur; -} - -void CharacterGenerator::modifyMenu() { - _vm->removeInputTop(); - printStats(_activeBox, 3); - - EoBCharacter *c = &_characters[_activeBox]; - int8 hpLO = c->hitPointsCur; - - for (int i = 0; i >= 0 && i < 7;) { - switch (i) { - case 0: - i = modifyStat(i, &c->strengthCur, &c->strengthExtCur); - break; - case 1: - i = modifyStat(i, &c->intelligenceCur, 0); - break; - case 2: - i = modifyStat(i, &c->wisdomCur, 0); - break; - case 3: - i = modifyStat(i, &c->dexterityCur, 0); - break; - case 4: - i = modifyStat(i, &c->constitutionCur, 0); - break; - case 5: - i = modifyStat(i, &c->charismaCur, 0); - break; - case 6: - hpLO = c->hitPointsCur; - i = modifyStat(i, &hpLO, 0); - c->hitPointsCur = hpLO; - break; - default: - break; - } - - if (i == -2 || _vm->shouldQuit()) - break; - else if (i < 0) - i = 6; - i %= 7; - - printStats(_activeBox, 3); - } - - printStats(_activeBox, 1); -} - -void CharacterGenerator::statsAndFacesMenu() { - faceSelectMenu(); - printStats(_activeBox, 1); - initButtonsFromList(27, 4); - _vm->removeInputTop(); - int in = 0; - - while (!in && !_vm->shouldQuit()) { - updateMagicShapes(); - in = getInput(_vm->_activeButtons); - _vm->removeInputTop(); - - if (in == 0x8001) { - processSpecialButton(4); - updateMagicShapes(); - generateStats(_activeBox); - in = -1; - } else if (in == 0x8002) { - processSpecialButton(7); - modifyMenu(); - in = -1; - } else if (in == 0x8003) { - processSpecialButton(8); - faceSelectMenu(); - in = -1; - } else if (in == 0x8004 || in == _vm->_keyMap[Common::KEYCODE_KP5]) { - processSpecialButton(6); - in = 1; - } else { - in = 0; - } - - if (in & 0x8000) { - printStats(_activeBox, 1); - initButtonsFromList(27, 4); - in = 0; - } - } - - _vm->_gui->updateBoxFrameHighLight(6 + _activeBox); - _vm->_gui->updateBoxFrameHighLight(-1); -} - -void CharacterGenerator::faceSelectMenu() { - int8 sp[4]; - memcpy(sp, _chargenSelectedPortraits2, sizeof(sp)); - _vm->removeInputTop(); - initButtonsFromList(21, 6); - - int charSex = _characters[_activeBox].raceSex % 2; - int8 shp = charSex ? 26 : 0; - - printStats(_activeBox, 4); - toggleSpecialButton(12, 0, 0); - toggleSpecialButton(13, 0, 0); - _vm->_gui->updateBoxFrameHighLight(-1); - - shp = getNextFreeFaceShape(shp, charSex, 1, _chargenSelectedPortraits); - - int res = -1; - int box = 1; - - while (res == -1 && !_vm->shouldQuit()) { - int8 shpOld = shp; - - for (int i = 0; i < 4; i++) { - sp[i] = shp; - _screen->drawShape(0, _faceShapes[sp[i]], 176 + (i << 5), 66, 0); - shp = getNextFreeFaceShape(shp + 1, charSex, 1, _chargenSelectedPortraits); - } - - shp = shpOld; - int in = 0; - - while (!in && !_vm->shouldQuit()) { - updateMagicShapes(); - in = getInput(_vm->_activeButtons); - _vm->removeInputTop(); - - _vm->_gui->updateBoxFrameHighLight(box + 10); - - if (in == 0x8002 || in == _vm->_keyMap[Common::KEYCODE_RIGHT]) { - processSpecialButton(13); - in = 2; - } else if (in > 0x8002 && in < 0x8007) { - box = (in & 7) - 3; - in = 3; - } else if (in == 0x8001 || in == _vm->_keyMap[Common::KEYCODE_LEFT]) { - processSpecialButton(12); - in = 1; - } else if (in == _vm->_keyMap[Common::KEYCODE_RETURN] || in == _vm->_keyMap[Common::KEYCODE_KP5]) { - in = 3; - } else if (in & 0x8000) { - in &= 0xFF; - } else { - in = 0; - } - } - - _vm->_gui->updateBoxFrameHighLight(-1); - - if (in == 1) - shp = getNextFreeFaceShape(shp - 1, charSex, -1, _chargenSelectedPortraits); - else if (in == 2) - shp = getNextFreeFaceShape(shp + 1, charSex, 1, _chargenSelectedPortraits); - else if (in == 3) - res = box; - } - - if (!_vm->shouldQuit()) { - _vm->_gui->updateBoxFrameHighLight(-1); - updateMagicShapes(); - - _chargenSelectedPortraits[_activeBox] = sp[res]; - _characters[_activeBox].portrait = sp[res]; - _characters[_activeBox].faceShape = _faceShapes[sp[res]]; - - printStats(_activeBox, 1); - } -} - -int CharacterGenerator::getNextFreeFaceShape(int shpIndex, int charSex, int step, int8 *selectedPortraits) { - int shpCur = ((shpIndex < 0) ? 43 : shpIndex) % 44; - bool notUsable = false; - - do { - notUsable = false; - for (int i = 0; i < 4; i++) { - if (_characters[i].name[0] && selectedPortraits[i] == shpCur) - notUsable = true; - } - - if ((charSex && (shpCur < 26)) || (!charSex && (shpCur > 28))) - notUsable = true; - - if (notUsable) { - shpCur += step; - shpCur = ((shpCur < 0) ? 43 : shpCur) % 44; - } - } while (notUsable); - - return shpCur; -} - -void CharacterGenerator::processFaceMenuSelection(int index) { - _vm->_gui->updateBoxFrameHighLight(-1); - if (index <= 48) - _screen->drawShape(0, _characters[_activeBox].faceShape, _chargenBoxX[_activeBox], _chargenBoxY[_activeBox] + 1, 0); - else - toggleSpecialButton(index - 50, 0, 0); -} - -void CharacterGenerator::printStats(int index, int mode) { - _screen->copyRegion(0, 0, 160, 0, 160, 128, 2, 2, Screen::CR_NO_P_CHECK); - _screen->_curPage = 2; - - EoBCharacter *c = &_characters[index]; - - if (mode != 4) - _screen->drawShape(2, c->faceShape, 224, 2, 0); - - _screen->printShadedText(c->name, 160 + ((160 - _screen->getTextWidth(c->name)) / 2), 35, 15, 0); - _screen->printShadedText(_chargenRaceSexStrings[c->raceSex], 160 + ((20 - strlen(_chargenRaceSexStrings[c->raceSex])) << 2), 45, 15, 0); - _screen->printShadedText(_chargenClassStrings[c->cClass], 160 + ((20 - strlen(_chargenClassStrings[c->cClass])) << 2), 54, 15, 0); - - for (int i = 0; i < 6; i++) - _screen->printShadedText(_chargenStatStrings[i], 163, (i + 8) << 3, 15, 0); - - _screen->printShadedText(_chargenStrings1[2], 248, 64, 15, 0); - - Common::String str = Common::String::format(_chargenStrings1[3], _vm->getCharStrength(c->strengthCur, c->strengthExtCur).c_str(), c->intelligenceCur, c->wisdomCur, c->dexterityCur, c->constitutionCur, c->charismaCur); - _screen->printShadedText(str.c_str(), 192, 64, 15, 0); - - str = Common::String::format(_chargenStrings1[4], c->armorClass, c->hitPointsMax); - _screen->printShadedText(str.c_str(), 280, 64, 15, 0); - - const char *lvlStr = c->level[2] ? _chargenStrings1[7] : (c->level[1] ? _chargenStrings1[6] : _chargenStrings1[5]); - str = Common::String::format(lvlStr, c->level[0], c->level[1], c->level[2]); - _screen->printShadedText(str.c_str(), 280, 80, 15, 0); - - switch (mode) { - case 1: - toggleSpecialButton(4, 0, 2); - toggleSpecialButton(7, 0, 2); - toggleSpecialButton(8, 0, 2); - toggleSpecialButton(6, 0, 2); - break; - - case 2: - toggleSpecialButton(16, 0, 2); - toggleSpecialButton(9, 0, 2); - break; - - case 3: - toggleSpecialButton(10, 0, 2); - toggleSpecialButton(11, 0, 2); - toggleSpecialButton(9, 0, 2); - break; - - default: - break; - } - - _screen->copyRegion(160, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); - - if (mode != 3) - _screen->updateScreen(); - - _screen->_curPage = 0; -} - -void CharacterGenerator::processNameInput(int index, int textColor) { - Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); - _screen->fillRect(_chargenNameFieldX[index], _chargenNameFieldY[index], _chargenNameFieldX[index] + 59, _chargenNameFieldY[index] + 5, 12); - int xOffs = (60 - _screen->getTextWidth(_characters[index].name)) >> 1; - _screen->printText(_characters[index].name, _chargenNameFieldX[index] + xOffs, _chargenNameFieldY[index], textColor, 0); - _screen->updateScreen(); - _screen->setFont(of); -} - -int CharacterGenerator::rollDice() { - int res = 0; - int min = 10; - - for (int i = 0; i < 4; i++) { - int d = _vm->rollDice(1, 6, 0); - res += d; - if (d < min) - min = d; - } - - res -= min; - return res; -} - -int CharacterGenerator::modifyStat(int index, int8 *stat1, int8 *stat2) { - uint8 *s1 = (uint8 *)stat1; - uint8 *s2 = (uint8 *)stat2; - - initButtonsFromList(31, 10); - Button *b = _vm->gui_getButton(_vm->_activeButtons, index + 1); - - printStats(_activeBox, 3); - _vm->removeInputTop(); - - Common::String statStr = index ? Common::String::format("%d", *s1) : _vm->getCharStrength(*s1, *s2); - - _screen->copyRegion(b->x - 112, b->y - 64, b->x + 32, b->y, 40, b->height, 2, 0, Screen::CR_NO_P_CHECK); - _screen->printShadedText(statStr.c_str(), b->x + 32, b->y, 6, 0); - _screen->updateScreen(); - - EoBCharacter *c = &_characters[_activeBox]; - - int ci = index; - uint8 v2 = s2 ? *s2 : 0; - - if (index == 6) { - _chargenMaxStats[6] = getMaxHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); - _chargenMinStats[6] = getMinHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); - } - - for (bool loop = true; loop && !_vm->shouldQuit();) { - uint8 v1 = *s1; - updateMagicShapes(); - int inputFlag = getInput(_vm->_activeButtons); - _vm->removeInputTop(); - - if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP4] || inputFlag == _vm->_keyMap[Common::KEYCODE_MINUS] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP_MINUS] || inputFlag == 0x8009) { - processSpecialButton(11); - v1--; - - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP6] || inputFlag == _vm->_keyMap[Common::KEYCODE_PLUS] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP_PLUS] || inputFlag == 0x8008) { - processSpecialButton(10); - v1++; - - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP8]) { - ci = (ci - 1) % 7; - loop = false; - - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP2]) { - ci = (ci + 1) % 7; - loop = false; - - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_o] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP5] || inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE] || inputFlag == 0x800A) { - processSpecialButton(9); - loop = false; - ci = -2; - - } else if (inputFlag & 0x8000) { - inputFlag = (inputFlag & 0x0F) - 1; - if (index != inputFlag) { - ci = inputFlag; - loop = false; - } - } - - if (v1 == *s1) - continue; - - if (!index) { - while (v1 > 18) { - v1--; - v2++; - } - while (v2 > 0 && v1 < 18) { - v1++; - v2--; - } - - v1 = CLIP(v1, _chargenMinStats[index], _chargenMaxStats[index] & 0xFF); - v2 = (v1 == 18 && _chargenMaxStats[index] >= 19) ? CLIP(v2, 0, 100) : 0; - if (s2) - *s2 = v2; - else - error("CharacterGenerator::modifyStat:..."); - } else { - v1 = CLIP(v1, _chargenMinStats[index], _chargenMaxStats[index]); - } - - *s1 = v1; - - if (index == 6) - _characters[_activeBox].hitPointsMax = v1; - - statStr = index ? Common::String::format("%d", *s1) : _vm->getCharStrength(*s1, *s2); - - _screen->copyRegion(b->x - 112, b->y - 64, b->x + 32, b->y, 40, b->height, 2, 0, Screen::CR_NO_P_CHECK); - _screen->printShadedText(statStr.c_str(), b->x + 32, b->y, 6, 0); - _screen->updateScreen(); - - if (index == 4) { - int oldVal = c->hitPointsCur; - _chargenMaxStats[6] = getMaxHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); - _chargenMinStats[6] = getMinHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); - c->hitPointsMax = c->hitPointsCur = CLIP(c->hitPointsCur, _chargenMinStats[6], _chargenMaxStats[6]); - - if (c->hitPointsCur != oldVal) { - statStr = Common::String::format("%d", c->hitPointsCur); - _screen->copyRegion(120, 72, 264, 136, 40, 8, 2, 0, Screen::CR_NO_P_CHECK); - _screen->printShadedText(statStr.c_str(), 264, 136, 15, 0); - _screen->updateScreen(); - } - - } else if (index == 3) { - int oldVal = c->armorClass; - c->armorClass = _vm->getDexterityArmorClassModifier(v1) + 10; - - if (c->armorClass != oldVal) { - statStr = Common::String::format("%d", c->armorClass); - _screen->copyRegion(120, 64, 264, 128, 40, 8, 2, 0, Screen::CR_NO_P_CHECK); - _screen->printShadedText(statStr.c_str(), 264, 128, 15, 0); - _screen->updateScreen(); - } - } - - if (loop == false) { - statStr = index ? Common::String::format("%d", *s1) : _vm->getCharStrength(*s1, *s2); - _screen->printText(statStr.c_str(), b->x + 32, b->y, 15, 0); - _screen->updateScreen(); - } - } - - return ci; -} - -int CharacterGenerator::getMaxHp(int cclass, int constitution, int level1, int level2, int level3) { - int res = 0; - constitution = _vm->getClassAndConstHitpointsModifier(cclass, constitution); - - int m = _vm->getCharacterClassType(cclass, 0); - if (m != -1) - res = _vm->getModifiedHpLimits(m, constitution, level1, false); - - m = _vm->getCharacterClassType(cclass, 1); - if (m != -1) - res += _vm->getModifiedHpLimits(m, constitution, level2, false); - - m = _vm->getCharacterClassType(cclass, 2); - if (m != -1) - res += _vm->getModifiedHpLimits(m, constitution, level3, false); - - res /= _vm->_numLevelsPerClass[cclass]; - - return res; -} - -int CharacterGenerator::getMinHp(int cclass, int constitution, int level1, int level2, int level3) { - int res = 0; - constitution = _vm->getClassAndConstHitpointsModifier(cclass, constitution); - - int m = _vm->getCharacterClassType(cclass, 0); - if (m != -1) - res = _vm->getModifiedHpLimits(m, constitution, level1, true); - - m = _vm->getCharacterClassType(cclass, 1); - if (m != -1) - res += _vm->getModifiedHpLimits(m, constitution, level2, true); - - m = _vm->getCharacterClassType(cclass, 2); - if (m != -1) - res += _vm->getModifiedHpLimits(m, constitution, level3, true); - - res /= _vm->_numLevelsPerClass[cclass]; - - return res; -} - -void CharacterGenerator::finish() { - _screen->copyRegion(0, 0, 160, 0, 160, 128, 2, 2, Screen::CR_NO_P_CHECK); - int cp = _screen->setCurPage(2); - _screen->printShadedText(_chargenEnterGameStrings[0], (_vm->gameFlags().platform == Common::kPlatformFMTowns) ? 184 : 168, 32, 15, 0); - _screen->setCurPage(cp); - _screen->copyRegion(160, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); - _screen->updateScreen(); - - if (_vm->game() == GI_EOB1) { - static const int8 classDefaultItemsList[] = { - 1, 17, 2, 17, 46, -1, 4, -1, 5, -1, 6, - 2, 7, -1, 8, -1, 9, 21, 10, 2, 31, 2 - }; - - static const int8 classDefaultItemsListIndex[] = { - 4, 8, 0, -1, 4, 3, 0, -1, 4, 10, - 0, 8, 3, 6, 1, -1, 2, 7, 0, -1, - 4, 5, 0, -1, 4, 7, 0, 8, 4, 5, - 0, 8, 4, 6, 8, 8, 4, 6, 5, 8, - 3, 6, 5, -1, 2, 7, 5, 0, 4, 6, - 7, 0, 4, 3, 7, 0, 2, 6, 7, 1 - }; - - _characters[0].inventory[2] = _vm->duplicateItem(35); - - for (int i = 0; i < 4; i++) { - EoBCharacter *c = &_characters[i]; - c->flags = 1; - c->food = 100; - c->id = i; - c->inventory[3] = _vm->duplicateItem(10); - - for (int ii = 0; ii < 4; ii++) { - int l = classDefaultItemsListIndex[(c->cClass << 2) + ii] << 1; - if (classDefaultItemsList[l] == -1) - continue; - - int d = classDefaultItemsList[l]; - int slot = classDefaultItemsList[l + 1]; - - if (slot < 0) { - slot = 0; - if (c->inventory[slot]) - slot++; - if (c->inventory[slot]) - slot++; - } - - if (slot != 2 && c->inventory[slot]) - continue; - - if (d == 5 && (c->raceSex >> 1) == 3) - d = 36; - - if (slot == 2) { - while (c->inventory[slot]) - slot++; - } - - c->inventory[slot] = _vm->duplicateItem(d); - } - - _vm->recalcArmorClass(i); - } - - } else { - static const uint8 classDefaultItemsListIndex[] = { 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 3, 2, 0, 0, 2 }; - static const int8 itemList0[] = { 3, 36, 0, 17, -1, 0, 0, 56, 1, 17, 31, 0, 1, 23, 1, 17, 31, 1, 1 }; - static const int8 itemList1[] = { 1, 2, 0, 17, -1, 0, 0 }; - static const int8 itemList2[] = { 2, 56, 1, 17, 31, 0, 1, 23, 1, 17, 31, 0, 1 }; - static const int8 itemList3[] = { 2, 1, 1, 17, 31, 1, 1, 1, 0, 17, 31, 2, 1 }; - static const int8 *const itemList[] = { itemList0, itemList1, itemList2, itemList3 }; - - for (int i = 0; i < 4; i++) { - EoBCharacter *c = &_characters[i]; - c->flags = 1; - c->food = 100; - c->id = i; - const int8 *df = itemList[classDefaultItemsListIndex[c->cClass]]; - int v1 = _vm->rollDice(1, *df++, -1); - - df = &df[v1 * 6]; - for (int ii = 0; ii < 2; ii++) { - if (df[0] == -1) - break; - _vm->createInventoryItem(c, df[0], df[1], df[2]); - df = &df[3]; - } - - uint16 m = _vm->_classModifierFlags[c->cClass]; - v1 = _vm->rollDice(1, 2, -1); - - int ival = 0; - int itype = 0; - - if (m & 0x31) { - if ((c->raceSex >> 1) == 3) { - itype = 22; - ival = 1; - } else { - if (v1 == 0) { - itype = 5; - ival = 1; - } else { - itype = 34; - } - } - } else if (m & 0x04) { - itype = 26; - if (v1 != 0) - ival = 1; - } else if (m & 0x08) { - ival = 1; - itype = ((c->raceSex >> 1) == 3) ? 22 : 5; - } else { - if (v1 == 0) { - itype = 3; - } else { - itype = 4; - ival = 1; - } - } - - _vm->createInventoryItem(c, itype, ival, 0); - _vm->createInventoryItem(c, 10, -1, 2); - _vm->createInventoryItem(c, 10, -1, 2); - _vm->createInventoryItem(c, 24, 2, 2); - - if (_vm->_classModifierFlags[c->cClass] & 2) { - _vm->createInventoryItem(c, 7, -1, 1); - _vm->createInventoryItem(c, 21, 4, 2); - _vm->createInventoryItem(c, 21, 13, 2); - } - - if (_vm->_classModifierFlags[c->cClass] & 0x14) { - if (c->cClass == 2) - _vm->createInventoryItem(c, 27, -1, 1); - else - _vm->createInventoryItem(c, 8, -1, 1); - - _vm->createInventoryItem(c, 20, 49, 1); - } - - if (_vm->_classModifierFlags[c->cClass] & 8) - _vm->createInventoryItem(c, 6, -1, 1); - - if (i == 0) - _vm->createInventoryItem(c, 93, -1, 2); - - _vm->recalcArmorClass(i); - } - } - - for (int i = 0; i < 4; i++) { - if (_vm->_classModifierFlags[_characters[i].cClass] & 2) - _characters[i].mageSpellsAvailableFlags = (_vm->game() == GI_EOB2) ? 0x81CB6 : 0x26C; - - if (_vm->_classModifierFlags[_characters[i].cClass] & 0x14 && _vm->game() == GI_EOB2) { - // Cleric/Paladin: Add Turn Undead spell - _characters[i].clericSpells[0] = 29; - } - } - - for (int i = 0; i < 4; i++) { - EoBCharacter *c = &_characters[i]; - c->strengthMax = c->strengthCur; - c->strengthExtMax = c->strengthExtCur; - c->intelligenceMax = c->intelligenceCur; - c->wisdomMax = c->wisdomCur; - c->dexterityMax = c->dexterityCur; - c->constitutionMax = c->constitutionCur; - c->charismaMax = c->charismaCur; - c->hitPointsMax = c->hitPointsCur; - } - - _vm->gui_resetButtonList(); - - if (_faceShapes) { - for (int i = 0; i < 44; i++) { - bool del = true; - for (int ii = 0; ii < 4; ii++) { - if (_characters[ii].faceShape == _faceShapes[i]) - del = false; - } - if (del) - delete[] _faceShapes[i]; - } - delete[] _faceShapes; - _faceShapes = 0; - } - - if (_chargenMagicShapes) { - for (int i = 0; i < 10; i++) - delete[] _chargenMagicShapes[i]; - delete[] _chargenMagicShapes; - _chargenMagicShapes = 0; - } - - for (int i = 0; i < 17; i++) { - delete[] _chargenButtonLabels[i]; - _chargenButtonLabels[i] = 0; - } -} - -const EoBChargenButtonDef CharacterGenerator::_chargenButtonDefsDOS[] = { - { 0x01, 0x37, 0x31, 0x32, 0x70 }, - { 0x09, 0x37, 0x31, 0x32, 0x71 }, - { 0x01, 0x77, 0x31, 0x32, 0x72 }, - { 0x09, 0x77, 0x31, 0x32, 0x73 }, - { 0x03, 0xB5, 0x53, 0x10, 0x1A }, - { 0x21, 0xAC, 0x26, 0x10, 0x19 }, - { 0x1C, 0xAC, 0x26, 0x10, 0x21 }, - { 0x21, 0xAC, 0x26, 0x10, 0x32 }, - { 0x13, 0x50, 0x9A, 0x08, 0x00 }, - { 0x13, 0x58, 0x9A, 0x08, 0x00 }, - { 0x13, 0x60, 0x9A, 0x08, 0x00 }, - { 0x13, 0x68, 0x9A, 0x08, 0x00 }, - { 0x13, 0x70, 0x9A, 0x08, 0x00 }, - { 0x13, 0x78, 0x9A, 0x08, 0x00 }, - { 0x13, 0x80, 0x9A, 0x08, 0x00 }, - { 0x13, 0x88, 0x9A, 0x08, 0x00 }, - { 0x13, 0x90, 0x9A, 0x08, 0x00 }, - { 0x13, 0x98, 0x9A, 0x08, 0x00 }, - { 0x13, 0xA0, 0x9A, 0x08, 0x00 }, - { 0x13, 0xA8, 0x9A, 0x08, 0x00 }, - { 0x13, 0xB0, 0x9A, 0x08, 0x00 }, - { 0x12, 0x42, 0x20, 0x10, 0x00 }, - { 0x12, 0x52, 0x20, 0x10, 0x00 }, - { 0x16, 0x42, 0x20, 0x20, 0x00 }, - { 0x1A, 0x42, 0x20, 0x20, 0x00 }, - { 0x1E, 0x42, 0x20, 0x20, 0x00 }, - { 0x22, 0x42, 0x20, 0x20, 0x00 }, - { 0x1C, 0x9C, 0x26, 0x10, 0x14 }, - { 0x21, 0x9C, 0x26, 0x10, 0x34 }, - { 0x1C, 0xAC, 0x26, 0x10, 0x22 }, - { 0x21, 0xAC, 0x26, 0x10, 0x26 }, - { 0x12, 0x80, 0x35, 0x08, 0x00 }, - { 0x12, 0x88, 0x35, 0x08, 0x00 }, - { 0x12, 0x90, 0x35, 0x08, 0x00 }, - { 0x12, 0x98, 0x35, 0x08, 0x00 }, - { 0x12, 0xA0, 0x35, 0x08, 0x00 }, - { 0x12, 0xA8, 0x35, 0x08, 0x00 }, - { 0x1D, 0x88, 0x35, 0x08, 0x00 }, - { 0x1B, 0xAC, 0x15, 0x10, 0x0D }, - { 0x1E, 0xAC, 0x15, 0x10, 0x0C }, - { 0x21, 0xAC, 0x25, 0x10, 0x19 } -}; - -const uint16 CharacterGenerator::_chargenButtonKeyCodesFMTOWNS[] = { - 93, 94, 95, 96, 80, 79, 68, 66, 82, 77, 70, 75, 43, 45, 79 -}; - -const CreatePartyModButton CharacterGenerator::_chargenModButtons[] = { - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40 }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x40 }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x80 }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0A, 0x80 }, - { 0x00, 0xC0, 0x04, 0x05, 0x07, 0x05, 0x08, 0x1C, 0x9C }, - { 0x04, 0xC0, 0x03, 0x05, 0x0A, 0x05, 0x08, 0x21, 0xAC }, - { 0x07, 0xC0, 0x03, 0x05, 0x0B, 0x05, 0x08, 0x21, 0xAC }, - { 0x0A, 0xC0, 0x04, 0x05, 0x06, 0x05, 0x08, 0x21, 0x9C }, - { 0x18, 0xC0, 0x03, 0x05, 0x09, 0x05, 0x08, 0x1C, 0xAC }, - { 0x0E, 0xC0, 0x02, 0x05, 0x0F, 0x05, 0x08, 0x21, 0xAC }, - { 0x10, 0xC0, 0x01, 0x05, 0x09, 0x05, 0x04, 0x1B, 0xAC }, - { 0x11, 0xC0, 0x01, 0x01, 0x09, 0x07, 0x04, 0x1E, 0xAC }, - { 0x12, 0xC0, 0x03, 0x07, 0x07, 0x04, 0x06, 0x12, 0x42 }, - { 0x15, 0xC0, 0x03, 0x07, 0x07, 0x04, 0x06, 0x12, 0x52 }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x03, 0xB5 }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x03, 0xB5 }, - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x1C, 0xAC } -}; - -const EoBRect8 CharacterGenerator::_chargenButtonBodyCoords[] = { - { 0x00, 0x80, 0x04, 0x20 }, - { 0x04, 0x80, 0x04, 0x20 }, - { 0x08, 0x80, 0x04, 0x20 }, - { 0x0C, 0x80, 0x04, 0x20 }, - { 0x0E, 0xA0, 0x03, 0x10 }, - { 0x0B, 0xA0, 0x03, 0x10 }, - { 0x10, 0x80, 0x04, 0x10 }, - { 0x10, 0x90, 0x04, 0x10 }, - { 0x11, 0xA0, 0x05, 0x10 }, - { 0x11, 0xB0, 0x05, 0x10 }, - { 0x16, 0xA0, 0x05, 0x10 }, - { 0x16, 0xB0, 0x05, 0x10 }, - { 0x00, 0xA0, 0x0B, 0x10 }, - { 0x14, 0x80, 0x0B, 0x10 }, - { 0x14, 0x90, 0x0B, 0x10 } -}; - -const int16 CharacterGenerator::_chargenBoxX[] = { 0x10, 0x50, 0x10, 0x50 }; -const int16 CharacterGenerator::_chargenBoxY[] = { 0x3F, 0x3F, 0x7F, 0x7F }; -const int16 CharacterGenerator::_chargenNameFieldX[] = { 0x02, 0x42, 0x02, 0x42 }; -const int16 CharacterGenerator::_chargenNameFieldY[] = { 0x6B, 0x6B, 0xAB, 0xAB }; - -const int32 CharacterGenerator::_classMenuMasks[] = { - 0x003F, 0x07BB, 0x77FB, 0x00F1, 0x08F1, 0x00B1 -}; - -const int32 CharacterGenerator::_alignmentMenuMasks[] = { - 0x01FF, 0x0007, 0x0001, 0x01FF, 0x01FF, 0x01FE, 0x01FF, 0x01FE, - 0x01FF, 0x01FE, 0x01FE, 0x01FE, 0x01FF, 0x0007, 0x01FF -}; - -const int16 CharacterGenerator::_raceModifiers[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, -1, 0, 1, -1, 0, 0, 0, -1, 0, 0, 1, 0, 0 -}; - -// Transfer Party - -class TransferPartyWiz { -public: - TransferPartyWiz(EoBCoreEngine *vm, Screen_EoB *screen); - ~TransferPartyWiz(); - - bool start(); - -private: - bool selectAndLoadTransferFile(); - bool transferFileDialogue(Common::String &dest); - - - int selectCharactersMenu(); - void drawCharPortraitWithStats(int charIndex, bool enabled); - void updateHighlight(int index); - - void convertStats(); - void convertInventory(); - Item convertItem(Item eob1Item); - void giveKhelbensCoin(); - - EoBCoreEngine *_vm; - Screen_EoB *_screen; - - int _highlight; - EoBItem *_oldItems; - - const uint16 *_portraitFrames; - const uint8 *_convertTable; - const uint8 *_itemTable; - const uint32 *_expTable; - const char *const *_strings1; - const char *const *_strings2; - const char *const *_labels; -}; - -TransferPartyWiz::TransferPartyWiz(EoBCoreEngine *vm, Screen_EoB *screen) : _vm(vm), _screen(screen) { - int temp; - _portraitFrames = _vm->staticres()->loadRawDataBe16(kEoB2TransferPortraitFrames, temp); - _convertTable = _vm->staticres()->loadRawData(kEoB2TransferConvertTable, temp); - _itemTable = _vm->staticres()->loadRawData(kEoB2TransferItemTable, temp); - _expTable = _vm->staticres()->loadRawDataBe32(kEoB2TransferExpTable, temp); - _strings1 = _vm->staticres()->loadStrings(kEoB2TransferStrings1, temp); - _strings2 = _vm->staticres()->loadStrings(kEoB2TransferStrings2, temp); - _labels = _vm->staticres()->loadStrings(kEoB2TransferLabels, temp); - _highlight = -1; - _oldItems = 0; -} - -TransferPartyWiz::~TransferPartyWiz() { - delete[] _oldItems; -} - -bool TransferPartyWiz::start() { - _screen->copyPage(0, 12); - - if (!selectAndLoadTransferFile()) - return false; - - convertStats(); - - _oldItems = new EoBItem[600]; - memcpy(_oldItems, _vm->_items, sizeof(EoBItem) * 600); - _vm->loadItemDefs(); - - int selection = selectCharactersMenu(); - if (selection == 0) { - for (int i = 0; i < 6; i++) - delete[] _vm->_characters[i].faceShape; - memset(_vm->_characters, 0, sizeof(EoBCharacter) * 6); - return false; - } - - int ch = 0; - for (int i = 0; i < 6; i++) { - if (selection & (1 << i)) { - if (ch != i) { - delete[] _vm->_characters[ch].faceShape; - memcpy(&_vm->_characters[ch], &_vm->_characters[i], sizeof(EoBCharacter)); - _vm->_characters[i].faceShape = 0; - } - ch++; - } - } - memset(&_vm->_characters[4], 0, sizeof(EoBCharacter) * 2); - - convertInventory(); - giveKhelbensCoin(); - - return true; -} - -bool TransferPartyWiz::selectAndLoadTransferFile() { - do { - _screen->copyPage(12, 0); - if (transferFileDialogue(_vm->_savegameFilename)) - break; - } while (_vm->_gui->confirmDialogue2(15, 68, 1)); - - if (_vm->_savegameFilename.empty()) - return false; - - if (_vm->loadGameState(-1).getCode() != Common::kNoError) - return false; - - return true; -} - - bool TransferPartyWiz::transferFileDialogue(Common::String &dest) { - _vm->_gui->transferWaitBox(); - - Common::Array eobTargets; - const Common::ConfigManager::DomainMap dom = ConfMan.getGameDomains(); - - for (Common::ConfigManager::DomainMap::const_iterator i = dom.begin(); i != dom.end(); ++i) { - if (ConfMan.get("gameid", i->_key).equals("eob")) - eobTargets.push_back(i->_key); - _vm->updateInput(); - } - - if (eobTargets.empty()) - return false; - - Common::String target = _vm->_gui->transferTargetMenu(eobTargets); - _screen->copyPage(12, 0); - - if (target.empty()) - return true; - - dest = target + ".fin"; - Common::InSaveFile *in = _vm->_saveFileMan->openForLoading(dest); - if (in) { - delete in; - if (_vm->_gui->confirmDialogue2(15, -2, 1)) - return true; - } - - _screen->copyPage(12, 0); - - bool result = _vm->_gui->transferFileMenu(target, dest); - _screen->copyPage(12, 0); - - return result; -} - -int TransferPartyWiz::selectCharactersMenu() { - _screen->setCurPage(2); - _screen->setFont(Screen::FID_6_FNT); - _screen->clearCurPage(); - - _vm->gui_drawBox(0, 0, 320, 163, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); - _screen->printText(_strings2[0], 5, 3, 15, 0); - _screen->printText(_strings2[1], 5, 10, 15, 0); - - for (int i = 0; i < 6; i++) - drawCharPortraitWithStats(i, 0); - - _vm->gui_drawBox(4, 148, 43, 12, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); - _vm->gui_drawBox(272, 148, 43, 12, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); - - _screen->printShadedText(_labels[0], 9, 151, 15, 0); - _screen->printShadedText(_labels[1], 288, 151, 15, 0); - - _screen->setCurPage(0); - _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK); - _screen->updateScreen(); - - int selection = 0; - int highlight = 0; - bool update = false; - - for (bool loop = true; loop && (!_vm->shouldQuit());) { - int inputFlag = _vm->checkInput(0, false, 0) & 0x8FF; - _vm->removeInputTop(); - - if (inputFlag) { - if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT]) { - highlight ^= 1; - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP]) { - highlight -= 2; - if (highlight < 0) - highlight += 8; - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN]) { - highlight += 2; - if (highlight >= 8) - highlight -= 8; - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_RETURN] || inputFlag == _vm->_keyMap[Common::KEYCODE_SPACE]) { - update = true; - } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE]) { - update = true; - highlight = 6; - } else if (inputFlag == 199) { - for (int i = 0; i < 8; i++) { - int t = i << 2; - if (_vm->posWithinRect(_vm->_mouseX, _vm->_mouseY, _portraitFrames[t], _portraitFrames[t + 1], _portraitFrames[t + 2], _portraitFrames[t + 3])) { - highlight = i; - update = true; - break; - } - } - } - } - - updateHighlight(highlight); - - if (!update) - continue; - - update = false; - - if (highlight < 6) { - if (_vm->_characters[highlight].flags & 1) { - selection ^= (1 << highlight); - drawCharPortraitWithStats(highlight, (selection & (1 << highlight)) ? true : false); - _screen->updateScreen(); - } - continue; - } - - int x = (highlight - 6) * 268 + 4; - _vm->gui_drawBox(x, 148, 43, 12, _vm->guiSettings()->colors.fill, _vm->guiSettings()->colors.fill, -1); - _screen->updateScreen(); - _vm->_system->delayMillis(80); - _vm->gui_drawBox(x, 148, 43, 12, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, -1); - _screen->updateScreen(); - - if (highlight == 6 || _vm->shouldQuit()) { - _screen->setFont(Screen::FID_8_FNT); - return 0; - } - - int count = 0; - for (int i = 0; i < 6; i++) { - if (selection & (1 << i)) - count++; - } - - if (count == 4 || _vm->shouldQuit()) - loop = false; - else - _vm->_gui->messageDialogue(16, count < 4 ? 69 : 70, 6); - - _screen->updateScreen(); - } - - _screen->setFont(Screen::FID_8_FNT); - if (_vm->shouldQuit()) - return 0; - else - _vm->_gui->messageDialogue(16, 71, 6); - - return selection; -} - -void TransferPartyWiz::drawCharPortraitWithStats(int charIndex, bool enabled) { - int16 x = (charIndex % 2) * 159; - int16 y = (charIndex / 2) * 40; - EoBCharacter *c = &_vm->_characters[charIndex]; - - _screen->fillRect(x + 4, y + 24, x + 36, y + 57, 12); - _vm->gui_drawBox(x + 40, y + 24, 118, 34, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); - - if (!(c->flags & 1)) - return; - - _screen->drawShape(_screen->_curPage, c->faceShape, x + 4, y + 25, 0); - - int color1 = 15; - int color2 = 12; - - if (enabled) { - color1 = 6; - color2 = 15; - } else { - _screen->drawShape(_screen->_curPage, _vm->_disabledCharGrid, x + 4, y + 25, 0); - } - - _screen->printShadedText(c->name, x + 44, y + 27, color1, 0); - _screen->printText(_vm->_chargenRaceSexStrings[c->raceSex], x + 43, y + 36, color2, 0); - _screen->printText(_vm->_chargenClassStrings[c->cClass], x + 43, y + 43, color2, 0); - - Common::String tmp = Common::String::format(_strings1[0], c->level[0]); - for (int i = 1; i < _vm->_numLevelsPerClass[c->cClass]; i++) - tmp += Common::String::format(_strings1[1], c->level[i]); - _screen->printText(tmp.c_str(), x + 43, y + 50, color2, 0); -} - -void TransferPartyWiz::updateHighlight(int index) { - static const int16 xPos[] = { 9, 288 }; - if (_highlight > 5 && _highlight != index) - _screen->printText(_labels[_highlight - 6], xPos[_highlight - 6], 151, 15, 0); - - if (index < 6) { - _vm->_gui->updateBoxFrameHighLight(14 + index); - _highlight = index; - return; - } - - if (_highlight == index) - return; - - if (_highlight < 6) - _vm->_gui->updateBoxFrameHighLight(-1); - - _screen->printText(_labels[index - 6], xPos[index - 6], 151, 6, 0); - _screen->updateScreen(); - _highlight = index; -} - -void TransferPartyWiz::convertStats() { - for (int i = 0; i < 6; i++) { - EoBCharacter *c = &_vm->_characters[i]; - uint32 aflags = 0; - - for (int ii = 0; ii < 25; ii++) { - if (c->mageSpellsAvailableFlags & (1 << ii)) { - int8 f = (int8)_convertTable[ii + 1] - 1; - if (f != -1) - aflags |= (1 << f); - } - } - c->mageSpellsAvailableFlags = aflags; - - c->armorClass = 0; - c->disabledSlots = 0; - c->flags &= 1; - c->hitPointsCur = c->hitPointsMax; - c->food = 100; - - c->effectFlags = 0; - c->damageTaken = 0; - memset(c->clericSpells, 0, sizeof(int8) * 80); - memset(c->mageSpells, 0, sizeof(int8) * 80); - memset(c->timers, 0, sizeof(uint32) * 10); - memset(c->events, 0, sizeof(int8) * 10); - memset(c->effectsRemainder, 0, sizeof(uint8) * 4); - memset(c->slotStatus, 0, sizeof(int8) * 5); - - for (int ii = 0; ii < 3; ii++) { - int t = _vm->getCharacterClassType(c->cClass, ii); - if (t == -1) - continue; - if (c->experience[ii] > _expTable[t]) - c->experience[ii] = _expTable[t]; - } - } -} - -void TransferPartyWiz::convertInventory() { - for (int i = 0; i < 4; i++) { - EoBCharacter *c = &_vm->_characters[i]; - - for (int slot = 0; slot < 27; slot++) { - Item itm = c->inventory[slot]; - if (slot == 16) { - Item first = itm; - c->inventory[slot] = 0; - - for (bool forceLoop = true; (itm && (itm != first)) || forceLoop; itm = _oldItems[itm].prev) { - forceLoop = false; - _vm->setItemPosition(&c->inventory[slot], -2, convertItem(itm), 0); - } - } else { - c->inventory[slot] = convertItem(itm); - } - } - } -} - -Item TransferPartyWiz::convertItem(Item eob1Item) { - if (!eob1Item) - return 0; - - EoBItem *itm1 = &_oldItems[eob1Item]; - - if (!_itemTable[itm1->type]) - return 0; - - Item newItem = _vm->duplicateItem(1); - EoBItem *itm2 = &_vm->_items[newItem]; - bool match = false; - - itm2->flags = itm1->flags | 0x40; - itm2->icon = itm1->icon; - itm2->type = itm1->type; - itm2->level = 0xFF; - - switch (itm2->type) { - case 35: - itm1->value += 25; - // fall through - case 34: - itm2->value = _convertTable[itm1->value]; - if (!itm2->value) { - itm2->block = -1; - return 0; - } - break; - case 39: - itm2->value = itm1->value - 1; - break; - case 48: - if (itm1->value == 5) { - memset(itm2, 0, sizeof(EoBItem)); - itm2->block = -1; - return 0; - } - itm2->value = itm1->value; - itm2->flags = ((itm1->flags & 0x3F) + 3) | 0x40; - break; - case 18: - itm2->icon = 19; - // fall through - default: - itm2->value = itm1->value; - break; - } - - switch ((_vm->_itemTypes[itm2->type].extraProperties & 0x7F) - 1) { - case 0: - case 1: - case 2: - if (itm2->value) - itm2->flags |= 0x80; - break; - case 4: - case 5: - case 8: - case 9: - case 13: - case 15: - case 17: - itm2->flags |= 0x80; - break; - default: - break; - } - - for (int i = 1; i < 600; i++) { - if (i == 60 || i == 62 || i == 63 || i == 83) - continue; - EoBItem *tmp = &_vm->_items[i]; - if (tmp->level || tmp->block == -2 || tmp->type != itm2->type || tmp->icon != itm2->icon) - continue; - itm2->nameUnid = tmp->nameUnid; - itm2->nameId = tmp->nameId; - match = true; - break; - } - - if (!match) { - for (int i = 1; i < 600; i++) { - if (i == 60 || i == 62 || i == 63 || i == 83) - continue; - EoBItem *tmp = &_vm->_items[i]; - if (tmp->level || tmp->block == -2 || tmp->type != itm2->type) - continue; - itm2->nameUnid = tmp->nameUnid; - itm2->nameId = tmp->nameId; - match = true; - break; - } - } - - if (!match) { - memset(itm2, 0, sizeof(EoBItem)); - itm2->block = -1; - return 0; - } - - itm2->level = 0; - return newItem; -} - -void TransferPartyWiz::giveKhelbensCoin() { - bool success = false; - for (int i = 0; i < 4 && !success; i++) { - EoBCharacter *c = &_vm->_characters[i]; - - for (int slot = 2; slot < 16; slot++) { - if (c->inventory[slot]) - continue; - _vm->createInventoryItem(c, 93, -1, slot); - success = true; - break; - } - } - - if (!success) { - _vm->_characters[0].inventory[2] = 0; - _vm->createInventoryItem(&_vm->_characters[0], 93, -1, 2); - } -} - -// Start functions - -bool EoBCoreEngine::startCharacterGeneration() { - return CharacterGenerator(this, _screen).start(_characters, &_faceShapes); -} - -bool EoBCoreEngine::startPartyTransfer() { - return TransferPartyWiz(this, _screen).start(); -} - -} // End of namespace Kyra - -#endif // ENABLE_EOB diff --git a/engines/kyra/darkmoon.cpp b/engines/kyra/darkmoon.cpp deleted file mode 100644 index 12508546a0..0000000000 --- a/engines/kyra/darkmoon.cpp +++ /dev/null @@ -1,493 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifdef ENABLE_EOB - -#include "kyra/darkmoon.h" -#include "kyra/resource.h" -#include "kyra/sound.h" - -namespace Kyra { - -DarkMoonEngine::DarkMoonEngine(OSystem *system, const GameFlags &flags) : EoBCoreEngine(system, flags) { - _dscDoorType5Offs = 0; - _numSpells = 70; - _menuChoiceInit = 4; - - _kheldranStrings = _npcStrings[0] = _npcStrings[1] = _hornStrings = 0; - _utilMenuStrings = _ascii2SjisTables = _ascii2SjisTables2 = 0; - _npcShpData = _dscDoorType5Offs = _hornSounds = 0; - _dreamSteps = 0; -} - -DarkMoonEngine::~DarkMoonEngine() { -} - -Common::Error DarkMoonEngine::init() { - Common::Error err = EoBCoreEngine::init(); - if (err.getCode() != Common::kNoError) - return err; - - initStaticResource(); - - _monsterProps = new EoBMonsterProperty[10]; - - if (_configRenderMode == Common::kRenderEGA) { - Palette pal(16); - _screen->loadPalette(_egaDefaultPalette, pal, 16); - _screen->setScreenPalette(pal); - } - - _screen->loadPalette(_flags.platform == Common::kPlatformFMTowns ? "MENU.PAL" : "PALETTE.COL", _screen->getPalette(0)); - _screen->setScreenPalette(_screen->getPalette(0)); - - // adjust menu settings for EOB II FM-Towns - if (_flags.platform == Common::kPlatformFMTowns) { - _screen->modifyScreenDim(6, 10, 100, 21, 40); - _screen->modifyScreenDim(27, 0, 0, 21, 2); - } - - return Common::kNoError; -} - -void DarkMoonEngine::startupNew() { - _currentLevel = 4; - _currentSub = 0; - loadLevel(4, 0); - _currentBlock = 171; - _currentDirection = 2; - setHandItem(0); - EoBCoreEngine::startupNew(); -} - -void DarkMoonEngine::drawNpcScene(int npcIndex) { - const uint8 *shpDef = &_npcShpData[npcIndex << 3]; - for (int i = npcIndex; i != 255; i = shpDef[7]) { - shpDef = &_npcShpData[i << 3]; - _screen->_curPage = 2; - const uint8 *shp = _screen->encodeShape(READ_LE_UINT16(shpDef), shpDef[2], shpDef[3], shpDef[4]); - _screen->_curPage = 0; - _screen->drawShape(0, shp, 88 + shpDef[5] - (shp[2] << 2), 104 + shpDef[6] - shp[1], 5); - delete[] shp; - } -} - -void DarkMoonEngine::runNpcDialogue(int npcIndex) { - if (npcIndex == 0) { - snd_playSoundEffect(57); - if (npcJoinDialogue(0, 1, 3, 2)) - setScriptFlags(0x40); - } else if (npcIndex == 1) { - snd_playSoundEffect(53); - gui_drawDialogueBox(); - - _txt->printDialogueText(4, 0); - int r = runDialogue(-1, 2, _npcStrings[0][0], _npcStrings[0][1]) - 1; - - if (r == 0) { - snd_stopSound(); - delay(3 * _tickLength); - snd_playSoundEffect(91); - npcJoinDialogue(1, 5, 6, 7); - } else if (r == 1) { - setScriptFlags(0x20); - } - - } else if (npcIndex == 2) { - snd_playSoundEffect(55); - gui_drawDialogueBox(); - - _txt->printDialogueText(8, 0); - int r = runDialogue(-1, 2, _npcStrings[1][0], _npcStrings[1][1]) - 1; - - if (r == 0) { - if (rollDice(1, 2, -1)) - _txt->printDialogueText(9, _okStrings[0]); - else - npcJoinDialogue(2, 102, 103, 104); - setScriptFlags(8); - } else if (r == 1) { - _currentDirection = 0; - } - } -} - -void DarkMoonEngine::updateUsedCharacterHandItem(int charIndex, int slot) { - EoBItem *itm = &_items[_characters[charIndex].inventory[slot]]; - if (itm->type == 48 || itm->type == 62) { - if (itm->value == 5) - return; - int charges = itm->flags & 0x3F; - if (--charges) - --itm->flags; - else - deleteInventoryItem(charIndex, slot); - } else if (itm->type == 26 || itm->type == 34 || itm->type == 35) { - deleteInventoryItem(charIndex, slot); - } -} - -void DarkMoonEngine::generateMonsterPalettes(const char *file, int16 monsterIndex) { - int cp = _screen->setCurPage(2); - _screen->loadShapeSetBitmap(file, 3, 3); - uint8 tmpPal[16]; - uint8 newPal[16]; - - for (int i = 0; i < 6; i++) { - int dci = monsterIndex + i; - memcpy(tmpPal, _monsterShapes[dci] + 4, 16); - int colx = 302 + 3 * i; - - for (int ii = 0; ii < 16; ii++) { - uint8 col = _screen->getPagePixel(_screen->_curPage, colx, 184 + ii); - int iii = 0; - for (; iii < 16; iii++) { - if (tmpPal[iii] == col) { - newPal[ii] = iii; - break; - } - } - - if (iii == 16) - newPal[ii] = 0; - } - - for (int ii = 1; ii < 3; ii++) { - memcpy(tmpPal, _monsterShapes[dci] + 4, 16); - - for (int iii = 0; iii < 16; iii++) { - uint8 col = _screen->getPagePixel(_screen->_curPage, colx + ii, 184 + iii); - if (newPal[iii]) - tmpPal[newPal[iii]] = col; - } - - int c = i; - if (monsterIndex >= 18) - c += 6; - - c = (c << 1) + (ii - 1); - assert(c < 24); - memcpy(_monsterPalettes[c], tmpPal, 16); - } - } - - _screen->setCurPage(cp); -} - -void DarkMoonEngine::loadMonsterDecoration(Common::SeekableReadStream *stream, int16 monsterIndex) { - int len = stream->readUint16LE(); - Common::List activeDecorations; - - for (int i = 0; i < len; i++) { - for (int ii = 0; ii < 6; ii++) { - uint8 dc[6]; - stream->read(dc, 6); - if (!dc[2] || !dc[3]) - continue; - - SpriteDecoration *m = &_monsterDecorations[i * 6 + ii + monsterIndex]; - if (_flags.platform != Common::kPlatformFMTowns) - m->shp = _screen->encodeShape(dc[0], dc[1], dc[2], dc[3]); - m->x = (int8)dc[4]; - m->y = (int8)dc[5]; - activeDecorations.push_back(m); - } - } - - if (_flags.platform == Common::kPlatformFMTowns) { - while (!activeDecorations.empty()) { - activeDecorations.front()->shp = loadTownsShape(stream); - activeDecorations.pop_front(); - } - } -} - -void DarkMoonEngine::replaceMonster(int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem) { - uint8 flg = _levelBlockProperties[block].flags & 7; - - if (flg == 7 || _currentBlock == block || (flg && (_monsterProps[type].u30 || pos == 4))) - return; - - for (int i = 0; i < 30; i++) { - if (_monsters[i].block != block) - continue; - if (_monsters[i].pos == 4 || _monsterProps[_monsters[i].type].u30) - return; - } - - int index = -1; - int maxDist = 0; - - for (int i = 0; i < 30; i++) { - if (_monsters[i].hitPointsCur <= 0) { - index = i; - break; - } - - if (_monsters[i].flags & 0x40) - continue; - - // WORKAROUND for bug #3611077 (Dran's dragon transformation sequence triggered prematurely): - // The boss level and the mindflayer level share the same monster data. If you hang around - // long enough in the mindflayer level all 30 monster slots will be used up. When this - // happens it will trigger the dragon transformation sequence when Dran is moved around by script. - // We avoid removing Dran here by prefering monster slots occupied by monsters from another - // sub level. - if (_monsters[i].sub != _currentSub) { - index = i; - break; - } - - int dist = getBlockDistance(_monsters[i].block, _currentBlock); - - if (dist > maxDist) { - maxDist = dist; - index = i; - } - } - - if (index == -1) - return; - - if (_monsters[index].hitPointsCur > 0) - killMonster(&_monsters[index], false); - - initMonster(index, unit, block, pos, dir, type, shpIndex, mode, h2, randItem, fixedItem); -} - -bool DarkMoonEngine::killMonsterExtra(EoBMonsterInPlay *m) { - // WORKAROUND for bug #3611077 (see DarkMoonEngine::replaceMonster()) - // The mindflayers have monster type 0, just like Dran. Using a monster slot occupied by a mindflayer would trigger the dragon transformation - // sequence when all 30 monster slots are used up. We avoid this by checking for m->sub == 1. - if (_currentLevel == 16 && _currentSub == 1 && m->sub == 1 && (_monsterProps[m->type].capsFlags & 4)) { - if (m->type) { - _playFinale = true; - _runFlag = false; - delay(850); - } else { - m->hitPointsCur = 150; - m->curRemoteWeapon = 0; - m->numRemoteAttacks = 255; - m->shpIndex++; - m->type++; - seq_dranDragonTransformation(); - } - return false; - } - return true; -} - -const uint8 *DarkMoonEngine::loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs) { - _screen->loadShapeSetBitmap(filename, 3, 3); - for (int i = 0; i < 3; i++) { - _doorShapes[doorIndex * 3 + i] = _screen->encodeShape(READ_LE_UINT16(shapeDefs), READ_LE_UINT16(shapeDefs + 2), READ_LE_UINT16(shapeDefs + 4), READ_LE_UINT16(shapeDefs + 6)); - shapeDefs += 8; - } - - for (int i = 0; i < 2; i++) { - _doorSwitches[doorIndex * 3 + i].shp = _screen->encodeShape(READ_LE_UINT16(shapeDefs), READ_LE_UINT16(shapeDefs + 2), READ_LE_UINT16(shapeDefs + 4), READ_LE_UINT16(shapeDefs + 6)); - shapeDefs += 8; - _doorSwitches[doorIndex * 3 + i].x = *shapeDefs; - shapeDefs += 2; - _doorSwitches[doorIndex * 3 + i].y = *shapeDefs; - shapeDefs += 2; - } - _screen->_curPage = 0; - return shapeDefs; -} - -void DarkMoonEngine::drawDoorIntern(int type, int, int x, int y, int w, int wall, int mDim, int16, int16) { - int shapeIndex = type * 3 + 2 - mDim; - uint8 *shp = _doorShapes[shapeIndex]; - if (!shp) - return; - - if ((_doorType[type] == 0) || (_doorType[type] == 1)) { - y = _dscDoorY1[mDim] - shp[1]; - x -= (shp[2] << 2); - - if (_doorType[type] == 1) { - drawBlockObject(0, 2, shp, x, y, 5); - shp = _doorShapes[3 + shapeIndex]; - } - - y -= ((wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult1[mDim]); - - if (_specialWallTypes[wall] == 5) - y -= _dscDoorType5Offs[shapeIndex]; - - } else if (_doorType[type] == 2) { - x -= (shp[2] << 2); - y = _dscDoorY2[mDim] - ((wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult3[mDim]); - } - - drawBlockObject(0, 2, shp, x, y, 5); - - if (_doorType[type] == 2) { - shp = _doorShapes[shapeIndex + 3]; - y = _dscDoorFrameY2[mDim] - shp[1] + (((wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult3[mDim]) >> 1) - 1; - drawBlockObject(0, 2, shp, x, y, 5); - } - - if (_wllShapeMap[wall] == -1 && !_noDoorSwitch[type]) - drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); -} - -void DarkMoonEngine::restParty_npc() { - int insalId = -1; - int numChar = 0; - - for (int i = 0; i < 6; i++) { - if (!testCharacter(i, 1)) - continue; - if (testCharacter(i, 2) && _characters[i].portrait == -1) - insalId = i; - numChar++; - } - - if (insalId == -1 || numChar < 5) - return; - - removeCharacterFromParty(insalId); - if (insalId < 4) - exchangeCharacters(insalId, testCharacter(5, 1) ? 5 : 4); - - clearScriptFlags(6); - - if (!stripPartyItems(1, 1, 1, 1)) - stripPartyItems(2, 1, 1, 1); - stripPartyItems(31, 0, 1, 3); - stripPartyItems(39, 1, 0, 3); - stripPartyItems(47, 0, 1, 2); - - _items[createItemOnCurrentBlock(28)].value = 26; - - gui_drawPlayField(false); - gui_drawAllCharPortraitsWithStats(); - - _screen->setClearScreenDim(10); - _screen->set16bitShadingLevel(4); - gui_drawBox(_screen->_curDim->sx << 3, _screen->_curDim->sy, _screen->_curDim->w << 3, _screen->_curDim->h, guiSettings()->colors.frame1, guiSettings()->colors.frame2, -1); - gui_drawBox((_screen->_curDim->sx << 3) + 1, _screen->_curDim->sy + 1, (_screen->_curDim->w << 3) - 2, _screen->_curDim->h - 2, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); - _screen->set16bitShadingLevel(0); - _gui->messageDialogue2(11, 63, 6); - _gui->messageDialogue2(11, 64, 6); -} - -bool DarkMoonEngine::restParty_extraAbortCondition() { - if (_currentLevel != 3) - return false; - - seq_nightmare(); - - return true; -} - -void DarkMoonEngine::useHorn(int charIndex, int weaponSlot) { - int v = _items[_characters[charIndex].inventory[weaponSlot]].value - 1; - _txt->printMessage(_hornStrings[v]); - snd_playSoundEffect(_hornSounds[v]); -} - -bool DarkMoonEngine::checkPartyStatusExtra() { - if (checkScriptFlags(0x100000)) - seq_kheldran(); - return _gui->confirmDialogue2(14, 67, 1); -} - -void DarkMoonEngine::drawLightningColumn() { - int f = rollDice(1, 2, -1); - int y = 0; - - for (int i = 0; i < 6; i++) { - f ^= 1; - drawBlockObject(f, 2, _lightningColumnShape, 72, y, 5); - y += 64; - } -} - -int DarkMoonEngine::resurrectionSelectDialogue() { - countResurrectionCandidates(); - - _rrNames[_rrCount] = _abortStrings[0]; - _rrId[_rrCount++] = 99; - - int r = _rrId[runDialogue(-1, 9, _rrNames[0], _rrNames[1], _rrNames[2], _rrNames[3], _rrNames[4], _rrNames[5], _rrNames[6], _rrNames[7], _rrNames[8]) - 1]; - if (r == 99) - return 0; - - if (r < 0) { - r = -r; - if (prepareForNewPartyMember(33, r)) - initNpc(r - 1); - } else { - _characters[r].hitPointsCur = 1; - } - - return 1; -} - -int DarkMoonEngine::charSelectDialogue() { - int cnt = 0; - const char *namesList[7]; - memset(namesList, 0, 7 * sizeof(const char *)); - - for (int i = 0; i < 6; i++) { - if (!testCharacter(i, 3)) - continue; - namesList[cnt++] = _characters[i].name; - } - - namesList[cnt++] = _abortStrings[0]; - - int r = runDialogue(-1, 7, namesList[0], namesList[1], namesList[2], namesList[3], namesList[4], namesList[5], namesList[6]) - 1; - if (r == cnt - 1) - return 99; - - for (cnt = 0; cnt < 6; cnt++) { - if (!testCharacter(cnt, 3)) - continue; - if (--r < 0) - break; - } - return cnt; -} - -void DarkMoonEngine::characterLevelGain(int charIndex) { - EoBCharacter *c = &_characters[charIndex]; - int s = _numLevelsPerClass[c->cClass]; - for (int i = 0; i < s; i++) { - uint32 er = getRequiredExperience(c->cClass, i, c->level[i] + 1); - if (er == 0xFFFFFFFF) - continue; - - increaseCharacterExperience(charIndex, er - c->experience[i] + 1); - } -} - -const KyraRpgGUISettings *DarkMoonEngine::guiSettings() { - return (_flags.platform == Common::kPlatformFMTowns) ? &_guiSettingsFMTowns : &_guiSettingsDOS; -} - -} // End of namespace Kyra - -#endif // ENABLE_EOB diff --git a/engines/kyra/darkmoon.h b/engines/kyra/darkmoon.h deleted file mode 100644 index f7065da8d6..0000000000 --- a/engines/kyra/darkmoon.h +++ /dev/null @@ -1,139 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifdef ENABLE_EOB - -#ifndef KYRA_EOB2_H -#define KYRA_EOB2_H - -#include "kyra/eobcommon.h" - -namespace Kyra { - -class DarkmoonSequenceHelper; - -struct DarkMoonAnimCommand { - uint8 command; - uint8 obj; - int16 x1; - uint8 y1; - uint8 delay; - uint8 pal; - uint8 x2; - uint8 y2; - uint8 w; - uint8 h; -}; - -class DarkMoonEngine : public EoBCoreEngine { -friend class GUI_EoB; -friend class DarkmoonSequenceHelper; -public: - DarkMoonEngine(OSystem *system, const GameFlags &flags); - ~DarkMoonEngine(); - -private: - // Init / Release - Common::Error init(); - void initStaticResource(); - void initSpells(); - - // Main Menu - int mainMenu(); - int mainMenuLoop(); - void townsUtilitiesMenu(); - - int _menuChoiceInit; - - // Main loop - void startupNew(); - void startupLoad() {} - - // Intro/Outro - void seq_playIntro(); - void seq_playFinale(); - void seq_playCredits(DarkmoonSequenceHelper *sq, const uint8 *data, int sd, int backupPage, int tempPage, int speed); - - // Ingame sequence - void seq_nightmare(); - void seq_kheldran(); - void seq_dranDragonTransformation(); - - const int8 *_dreamSteps; - const char *const *_kheldranStrings; - - // characters - void drawNpcScene(int npcIndex); - void runNpcDialogue(int npcIndex); - - const uint8 *_npcShpData; - const char *const *_npcStrings[2]; - - // items - void updateUsedCharacterHandItem(int charIndex, int slot); - - // Monsters - void generateMonsterPalettes(const char *file, int16 monsterIndex); - void loadMonsterDecoration(Common::SeekableReadStream *stream, int16 monsterIndex); - void replaceMonster(int unit, uint16 block, int d, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem); - bool killMonsterExtra(EoBMonsterInPlay *m); - - // Level - void loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2) {} - const uint8 *loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs); - void drawDoorIntern(int type, int, int x, int y, int w, int wall, int mDim, int16, int16); - - const uint8 *_dscDoorType5Offs; - - // Fight - static const uint8 _monsterAcHitChanceTbl1[]; - static const uint8 _monsterAcHitChanceTbl2[]; - - // Rest party - void restParty_npc(); - bool restParty_extraAbortCondition(); - - // misc - void useHorn(int charIndex, int weaponSlot); - bool checkPartyStatusExtra(); - void drawLightningColumn(); - int resurrectionSelectDialogue(); - int charSelectDialogue(); - void characterLevelGain(int charIndex); - - const KyraRpgGUISettings *guiSettings(); - - const char *const *_hornStrings; - const uint8 *_hornSounds; - - const char *const *_utilMenuStrings; - - static const KyraRpgGUISettings _guiSettingsDOS; - static const KyraRpgGUISettings _guiSettingsFMTowns; - static const uint8 _egaDefaultPalette[]; -}; - -} // End of namespace Kyra - -#endif - -#endif // ENABLE_EOB diff --git a/engines/kyra/debugger.cpp b/engines/kyra/debugger.cpp deleted file mode 100644 index d87106c839..0000000000 --- a/engines/kyra/debugger.cpp +++ /dev/null @@ -1,744 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "kyra/debugger.h" -#include "kyra/kyra_lok.h" -#include "kyra/kyra_hof.h" -#include "kyra/timer.h" -#include "kyra/resource.h" -#include "kyra/lol.h" -#include "kyra/eobcommon.h" - -#include "common/system.h" -#include "common/config-manager.h" - -namespace Kyra { - -Debugger::Debugger(KyraEngine_v1 *vm) - : ::GUI::Debugger(), _vm(vm) { -} - -void Debugger::initialize() { - registerCmd("continue", WRAP_METHOD(Debugger, cmdExit)); - registerCmd("screen_debug_mode", WRAP_METHOD(Debugger, cmdSetScreenDebug)); - registerCmd("load_palette", WRAP_METHOD(Debugger, cmdLoadPalette)); - registerCmd("facings", WRAP_METHOD(Debugger, cmdShowFacings)); - registerCmd("gamespeed", WRAP_METHOD(Debugger, cmdGameSpeed)); - registerCmd("flags", WRAP_METHOD(Debugger, cmdListFlags)); - registerCmd("toggleflag", WRAP_METHOD(Debugger, cmdToggleFlag)); - registerCmd("queryflag", WRAP_METHOD(Debugger, cmdQueryFlag)); - registerCmd("timers", WRAP_METHOD(Debugger, cmdListTimers)); - registerCmd("settimercountdown", WRAP_METHOD(Debugger, cmdSetTimerCountdown)); -} - -bool Debugger::cmdSetScreenDebug(int argc, const char **argv) { - if (argc > 1) { - if (scumm_stricmp(argv[1], "enable") == 0) - _vm->screen()->enableScreenDebug(true); - else if (scumm_stricmp(argv[1], "disable") == 0) - _vm->screen()->enableScreenDebug(false); - else - debugPrintf("Use screen_debug_mode to enable or disable it.\n"); - } else { - debugPrintf("Screen debug mode is %s.\n", (_vm->screen()->queryScreenDebug() ? "enabled" : "disabled")); - debugPrintf("Use screen_debug_mode to enable or disable it.\n"); - } - return true; -} - -bool Debugger::cmdLoadPalette(int argc, const char **argv) { - Palette palette(_vm->screen()->getPalette(0).getNumColors()); - - if (argc <= 1) { - debugPrintf("Use load_palette [start_col] [end_col]\n"); - return true; - } - - if (_vm->game() != GI_KYRA1 && _vm->resource()->getFileSize(argv[1]) != 768) { - uint8 *buffer = new uint8[320 * 200 * sizeof(uint8)]; - if (!buffer) { - debugPrintf("ERROR: Cannot allocate buffer for screen region!\n"); - return true; - } - - _vm->screen()->copyRegionToBuffer(5, 0, 0, 320, 200, buffer); - _vm->screen()->loadBitmap(argv[1], 5, 5, 0); - palette.copy(_vm->screen()->getCPagePtr(5), 0, 256); - _vm->screen()->copyBlockToPage(5, 0, 0, 320, 200, buffer); - - delete[] buffer; - } else if (!_vm->screen()->loadPalette(argv[1], palette)) { - debugPrintf("ERROR: Palette '%s' not found!\n", argv[1]); - return true; - } - - int startCol = 0; - int endCol = palette.getNumColors(); - if (argc > 2) - startCol = MIN(palette.getNumColors(), MAX(0, atoi(argv[2]))); - if (argc > 3) - endCol = MIN(palette.getNumColors(), MAX(0, atoi(argv[3]))); - - if (startCol > 0) - palette.copy(_vm->screen()->getPalette(0), 0, startCol); - if (endCol < palette.getNumColors()) - palette.copy(_vm->screen()->getPalette(0), endCol); - - _vm->screen()->setScreenPalette(palette); - _vm->screen()->updateScreen(); - - return true; -} - -bool Debugger::cmdShowFacings(int argc, const char **argv) { - debugPrintf("Facing directions:\n"); - debugPrintf("7 0 1\n"); - debugPrintf(" \\ | / \n"); - debugPrintf("6--*--2\n"); - debugPrintf(" / | \\\n"); - debugPrintf("5 4 3\n"); - return true; -} - -bool Debugger::cmdGameSpeed(int argc, const char **argv) { - if (argc == 2) { - int val = atoi(argv[1]); - - if (val < 1 || val > 1000) { - debugPrintf("speed must lie between 1 and 1000 (default: 60)\n"); - return true; - } - - _vm->_tickLength = (uint8)(1000.0 / val); - } else { - debugPrintf("Syntax: gamespeed \n"); - } - - return true; -} - -bool Debugger::cmdListFlags(int argc, const char **argv) { - for (int i = 0, p = 0; i < (int)sizeof(_vm->_flagsTable) * 8; i++, ++p) { - debugPrintf("(%-3i): %-2i", i, _vm->queryGameFlag(i)); - if (p == 5) { - debugPrintf("\n"); - p -= 6; - } - } - debugPrintf("\n"); - return true; -} - -bool Debugger::cmdToggleFlag(int argc, const char **argv) { - if (argc > 1) { - uint flag = atoi(argv[1]); - if (_vm->queryGameFlag(flag)) - _vm->resetGameFlag(flag); - else - _vm->setGameFlag(flag); - debugPrintf("Flag %i is now %i\n", flag, _vm->queryGameFlag(flag)); - } else { - debugPrintf("Syntax: toggleflag \n"); - } - - return true; -} - -bool Debugger::cmdQueryFlag(int argc, const char **argv) { - if (argc > 1) { - uint flag = atoi(argv[1]); - debugPrintf("Flag %i is %i\n", flag, _vm->queryGameFlag(flag)); - } else { - debugPrintf("Syntax: queryflag \n"); - } - - return true; -} - -bool Debugger::cmdListTimers(int argc, const char **argv) { - debugPrintf("Current time: %-8u\n", g_system->getMillis()); - for (int i = 0; i < _vm->timer()->count(); i++) - debugPrintf("Timer %-2i: Active: %-3s Countdown: %-6i %-8u\n", i, _vm->timer()->isEnabled(i) ? "Yes" : "No", _vm->timer()->getDelay(i), _vm->timer()->getNextRun(i)); - - return true; -} - -bool Debugger::cmdSetTimerCountdown(int argc, const char **argv) { - if (argc > 2) { - uint timer = atoi(argv[1]); - uint countdown = atoi(argv[2]); - _vm->timer()->setCountdown(timer, countdown); - debugPrintf("Timer %i now has countdown %i\n", timer, _vm->timer()->getDelay(timer)); - } else { - debugPrintf("Syntax: settimercountdown \n"); - } - - return true; -} - -#pragma mark - - -Debugger_LoK::Debugger_LoK(KyraEngine_LoK *vm) - : Debugger(vm), _vm(vm) { -} - -void Debugger_LoK::initialize() { - registerCmd("enter", WRAP_METHOD(Debugger_LoK, cmdEnterRoom)); - registerCmd("scenes", WRAP_METHOD(Debugger_LoK, cmdListScenes)); - registerCmd("give", WRAP_METHOD(Debugger_LoK, cmdGiveItem)); - registerCmd("birthstones", WRAP_METHOD(Debugger_LoK, cmdListBirthstones)); - Debugger::initialize(); -} - -bool Debugger_LoK::cmdEnterRoom(int argc, const char **argv) { - uint direction = 0; - if (argc > 1) { - int room = atoi(argv[1]); - - // game will crash if entering a non-existent room - if (room >= _vm->_roomTableSize) { - debugPrintf("room number must be any value between (including) 0 and %d\n", _vm->_roomTableSize - 1); - return true; - } - - if (argc > 2) { - direction = atoi(argv[2]); - } else { - if (_vm->_roomTable[room].northExit != 0xFFFF) - direction = 3; - else if (_vm->_roomTable[room].eastExit != 0xFFFF) - direction = 4; - else if (_vm->_roomTable[room].southExit != 0xFFFF) - direction = 1; - else if (_vm->_roomTable[room].westExit != 0xFFFF) - direction = 2; - } - - _vm->_system->hideOverlay(); - _vm->_currentCharacter->facing = direction; - - _vm->enterNewScene(room, _vm->_currentCharacter->facing, 0, 0, 1); - while (!_vm->_screen->isMouseVisible()) - _vm->_screen->showMouse(); - - detach(); - return false; - } - - debugPrintf("Syntax: room \n"); - return true; -} - -bool Debugger_LoK::cmdListScenes(int argc, const char **argv) { - for (int i = 0; i < _vm->_roomTableSize; i++) { - debugPrintf("%-3i: %-10s", i, _vm->_roomFilenameTable[_vm->_roomTable[i].nameIndex]); - if (!(i % 8)) - debugPrintf("\n"); - } - debugPrintf("\n"); - debugPrintf("Current room: %i\n", _vm->_currentRoom); - return true; -} - -bool Debugger_LoK::cmdGiveItem(int argc, const char **argv) { - if (argc == 2) { - int item = atoi(argv[1]); - - // Kyrandia 1 has only 108 items (-1 to 106), otherwise it will crash - if (item < -1 || item > 106) { - debugPrintf("'itemid' must be any value between (including) -1 and 106\n"); - return true; - } - - _vm->setMouseItem(item); - _vm->_itemInHand = item; - } else { - debugPrintf("Syntax: give \n"); - } - - return true; -} - -bool Debugger_LoK::cmdListBirthstones(int argc, const char **argv) { - debugPrintf("Needed birthstone gems:\n"); - for (int i = 0; i < ARRAYSIZE(_vm->_birthstoneGemTable); ++i) - debugPrintf("%-3d '%s'\n", _vm->_birthstoneGemTable[i], _vm->_itemList[_vm->_birthstoneGemTable[i]]); - return true; -} - -#pragma mark - - -Debugger_v2::Debugger_v2(KyraEngine_v2 *vm) : Debugger(vm), _vm(vm) { -} - -void Debugger_v2::initialize() { - registerCmd("character_info", WRAP_METHOD(Debugger_v2, cmdCharacterInfo)); - registerCmd("enter", WRAP_METHOD(Debugger_v2, cmdEnterScene)); - registerCmd("scenes", WRAP_METHOD(Debugger_v2, cmdListScenes)); - registerCmd("scene_info", WRAP_METHOD(Debugger_v2, cmdSceneInfo)); - registerCmd("scene_to_facing", WRAP_METHOD(Debugger_v2, cmdSceneToFacing)); - registerCmd("give", WRAP_METHOD(Debugger_v2, cmdGiveItem)); - Debugger::initialize(); -} - -bool Debugger_v2::cmdEnterScene(int argc, const char **argv) { - uint direction = 0; - if (argc > 1) { - int scene = atoi(argv[1]); - - // game will crash if entering a non-existent scene - if (scene >= _vm->_sceneListSize) { - debugPrintf("scene number must be any value between (including) 0 and %d\n", _vm->_sceneListSize - 1); - return true; - } - - if (argc > 2) { - direction = atoi(argv[2]); - } else { - if (_vm->_sceneList[scene].exit1 != 0xFFFF) - direction = 4; - else if (_vm->_sceneList[scene].exit2 != 0xFFFF) - direction = 6; - else if (_vm->_sceneList[scene].exit3 != 0xFFFF) - direction = 0; - else if (_vm->_sceneList[scene].exit4 != 0xFFFF) - direction = 2; - } - - _vm->_system->hideOverlay(); - _vm->_mainCharacter.facing = direction; - - _vm->enterNewScene(scene, _vm->_mainCharacter.facing, 0, 0, 1); - while (!_vm->screen_v2()->isMouseVisible()) - _vm->screen_v2()->showMouse(); - - detach(); - return false; - } - - debugPrintf("Syntax: %s \n", argv[0]); - return true; -} - -bool Debugger_v2::cmdListScenes(int argc, const char **argv) { - int shown = 1; - for (int i = 0; i < _vm->_sceneListSize; ++i) { - if (_vm->_sceneList[i].filename1[0]) { - debugPrintf("%-2i: %-10s", i, _vm->_sceneList[i].filename1); - if (!(shown % 5)) - debugPrintf("\n"); - ++shown; - } - } - debugPrintf("\n"); - debugPrintf("Current scene: %i\n", _vm->_currentScene); - return true; -} - -bool Debugger_v2::cmdSceneInfo(int argc, const char **argv) { - debugPrintf("Current scene: %d '%s'\n", _vm->_currentScene, _vm->_sceneList[_vm->_currentScene].filename1); - debugPrintf("\n"); - debugPrintf("Exit information:\n"); - debugPrintf("Exit1: leads to %d, position %dx%d\n", int16(_vm->_sceneExit1), _vm->_sceneEnterX1, _vm->_sceneEnterY1); - debugPrintf("Exit2: leads to %d, position %dx%d\n", int16(_vm->_sceneExit2), _vm->_sceneEnterX2, _vm->_sceneEnterY2); - debugPrintf("Exit3: leads to %d, position %dx%d\n", int16(_vm->_sceneExit3), _vm->_sceneEnterX3, _vm->_sceneEnterY3); - debugPrintf("Exit4: leads to %d, position %dx%d\n", int16(_vm->_sceneExit4), _vm->_sceneEnterX4, _vm->_sceneEnterY4); - debugPrintf("Special exit information:\n"); - if (!_vm->_specialExitCount) { - debugPrintf("No special exits.\n"); - } else { - debugPrintf("This scene has %d special exits.\n", _vm->_specialExitCount); - for (int i = 0; i < _vm->_specialExitCount; ++i) { - debugPrintf("SpecialExit%d: facing %d, position (x1/y1/x2/y2): %d/%d/%d/%d\n", i, - _vm->_specialExitTable[20 + i], _vm->_specialExitTable[0 + i], _vm->_specialExitTable[5 + i], - _vm->_specialExitTable[10 + i], _vm->_specialExitTable[15 + i]); - } - } - - return true; -} - -bool Debugger_v2::cmdCharacterInfo(int argc, const char **argv) { - debugPrintf("Main character is in scene: %d '%s'\n", _vm->_mainCharacter.sceneId, _vm->_sceneList[_vm->_mainCharacter.sceneId].filename1); - debugPrintf("Position: %dx%d\n", _vm->_mainCharacter.x1, _vm->_mainCharacter.y1); - debugPrintf("Facing: %d\n", _vm->_mainCharacter.facing); - debugPrintf("Inventory:\n"); - for (int i = 0; i < 20; ++i) { - debugPrintf("%-2d ", int8(_vm->_mainCharacter.inventory[i])); - if (i == 9 || i == 19) - debugPrintf("\n"); - } - return true; -} - -bool Debugger_v2::cmdSceneToFacing(int argc, const char **argv) { - if (argc == 2) { - int facing = atoi(argv[1]); - int16 exit = -1; - - switch (facing) { - case 0: case 1: case 7: - exit = _vm->_sceneList[_vm->_currentScene].exit1; - break; - - case 6: - exit = _vm->_sceneList[_vm->_currentScene].exit2; - break; - - case 3: case 4: case 5: - exit = _vm->_sceneList[_vm->_currentScene].exit3; - break; - - case 2: - exit = _vm->_sceneList[_vm->_currentScene].exit4; - break; - - default: - break; - } - - debugPrintf("Exit to facing %d leads to room %d.\n", facing, exit); - } else { - debugPrintf("Usage: %s \n", argv[0]); - } - - return true; -} - -bool Debugger_v2::cmdGiveItem(int argc, const char **argv) { - if (argc == 2) { - int item = atoi(argv[1]); - - if (item < -1 || item > _vm->engineDesc().maxItemId) { - debugPrintf("itemid must be any value between (including) -1 and %d\n", _vm->engineDesc().maxItemId); - return true; - } - - _vm->setHandItem(item); - } else { - debugPrintf("Syntax: give \n"); - } - - return true; -} - -#pragma mark - - -Debugger_HoF::Debugger_HoF(KyraEngine_HoF *vm) : Debugger_v2(vm), _vm(vm) { -} - -void Debugger_HoF::initialize() { - registerCmd("pass_codes", WRAP_METHOD(Debugger_HoF, cmdPasscodes)); - Debugger_v2::initialize(); -} - -bool Debugger_HoF::cmdPasscodes(int argc, const char **argv) { - if (argc == 2) { - int val = atoi(argv[1]); - - if (val < 0 || val > 1) { - debugPrintf("value must be either 1 (on) or 0 (off)\n"); - return true; - } - - _vm->_dbgPass = val; - } else { - debugPrintf("Syntax: pass_codes <0/1>\n"); - } - - return true; -} - -#pragma mark - - -#ifdef ENABLE_LOL -Debugger_LoL::Debugger_LoL(LoLEngine *vm) : Debugger(vm), _vm(vm) { -} -#endif // ENABLE_LOL - -#ifdef ENABLE_EOB -Debugger_EoB::Debugger_EoB(EoBCoreEngine *vm) : Debugger(vm), _vm(vm) { -} - -void Debugger_EoB::initialize() { - registerCmd("import_savefile", WRAP_METHOD(Debugger_EoB, cmdImportSaveFile)); - registerCmd("save_original", WRAP_METHOD(Debugger_EoB, cmdSaveOriginal)); - registerCmd("list_monsters", WRAP_METHOD(Debugger_EoB, cmdListMonsters)); - registerCmd("show_position", WRAP_METHOD(Debugger_EoB, cmdShowPosition)); - registerCmd("set_position", WRAP_METHOD(Debugger_EoB, cmdSetPosition)); - registerCmd("print_map", WRAP_METHOD(Debugger_EoB, cmdPrintMap)); - registerCmd("open_door", WRAP_METHOD(Debugger_EoB, cmdOpenDoor)); - registerCmd("close_door", WRAP_METHOD(Debugger_EoB, cmdCloseDoor)); - registerCmd("list_flags", WRAP_METHOD(Debugger_EoB, cmdListFlags)); - registerCmd("set_flag", WRAP_METHOD(Debugger_EoB, cmdSetFlag)); - registerCmd("clear_flag", WRAP_METHOD(Debugger_EoB, cmdClearFlag)); -} - -bool Debugger_EoB::cmdImportSaveFile(int argc, const char **argv) { - if (!_vm->_allowImport) { - debugPrintf("This command only works from the main menu.\n"); - return true; - } - - if (argc == 3) { - int slot = atoi(argv[1]); - if (slot < -1 || slot > 989) { - debugPrintf("slot must be between (including) -1 and 989 \n"); - return true; - } - - debugPrintf(_vm->importOriginalSaveFile(slot, argv[2]) ? "Success.\n" : "Failure.\n"); - _vm->loadItemDefs(); - } else { - debugPrintf("Syntax: import_savefile \n (Imports source save game file to dest slot.)\n import_savefile -1\n (Imports all original save game files found and puts them into the first available slots.)\n\n"); - } - - return true; -} - -bool Debugger_EoB::cmdSaveOriginal(int argc, const char **argv) { - if (!_vm->_runFlag) { - debugPrintf("This command doesn't work during intro or outro sequences,\nfrom the main menu or from the character generation.\n"); - return true; - } - - Common::String dir = ConfMan.get("savepath"); - if (dir == "None") - dir.clear(); - - Common::FSNode nd(dir); - if (!nd.isDirectory()) - return false; - - if (_vm->game() == GI_EOB1) { - if (argc == 1) { - if (_vm->saveAsOriginalSaveFile()) { - Common::FSNode nf = nd.getChild(Common::String::format("EOBDATA.SAV")); - if (nf.isReadable()) - debugPrintf("Saved to file: %s\n\n", nf.getPath().c_str()); - else - debugPrintf("Failure.\n"); - } else { - debugPrintf("Failure.\n"); - } - } else { - debugPrintf("Syntax: save_original\n (Saves game in original file format to a file which can be used with the original game executable.)\n\n"); - } - return true; - - } else if (argc == 2) { - int slot = atoi(argv[1]); - if (slot < 0 || slot > 5) { - debugPrintf("Slot must be between (including) 0 and 5.\n"); - } else if (_vm->saveAsOriginalSaveFile(slot)) { - Common::FSNode nf = nd.getChild(Common::String::format("EOBDATA%d.SAV", slot)); - if (nf.isReadable()) - debugPrintf("Saved to file: %s\n\n", nf.getPath().c_str()); - else - debugPrintf("Failure.\n"); - } else { - debugPrintf("Failure.\n"); - } - return true; - } - - debugPrintf("Syntax: save_original \n (Saves game in original file format to a file which can be used with the original game executable.\n A save slot between 0 and 5 must be specified.)\n\n"); - return true; -} - -bool Debugger_EoB::cmdListMonsters(int, const char **) { - debugPrintf("\nCurrent level: %d\n----------------------\n\n", _vm->_currentLevel); - debugPrintf("Id Type Unit Block Position Direction Sub Level Mode Dst.block HP Flags\n--------------------------------------------------------------------------------------------------------------\n"); - - for (int i = 0; i < 30; i++) { - EoBMonsterInPlay *m = &_vm->_monsters[i]; - debugPrintf("%.02d %.02d %.02d 0x%.04x %d %d %d %.02d 0x%.04x %.03d/%.03d 0x%.02x\n", i, m->type, m->unit, m->block, m->pos, m->dir, m->sub, m->mode, m->dest, m->hitPointsCur, m->hitPointsMax, m->flags); - } - - debugPrintf("\n"); - - return true; -} - -bool Debugger_EoB::cmdShowPosition(int, const char **) { - debugPrintf("\nCurrent level: %d\nCurrent Sub Level: %d\nCurrent block: %d (0x%.04x)\nNext block: %d (0x%.04x)\nCurrent direction: %d\n\n", _vm->_currentLevel, _vm->_currentSub, _vm->_currentBlock, _vm->_currentBlock, _vm->calcNewBlockPosition(_vm->_currentBlock, _vm->_currentDirection), _vm->calcNewBlockPosition(_vm->_currentBlock, _vm->_currentDirection), _vm->_currentDirection); - return true; -} - -bool Debugger_EoB::cmdSetPosition(int argc, const char **argv) { - if (argc == 4) { - _vm->_currentBlock = atoi(argv[3]); - int sub = atoi(argv[2]); - int level = atoi(argv[1]); - - int maxLevel = (_vm->game() == GI_EOB1) ? 12 : 16; - if (level < 1 || level > maxLevel) { - debugPrintf(" must be a value from 1 to %d.\n\n", maxLevel); - return true; - } - - if (level != _vm->_currentLevel || sub != _vm->_currentSub) { - _vm->completeDoorOperations(); - _vm->generateTempData(); - _vm->txt()->removePageBreakFlag(); - _vm->screen()->setScreenDim(7); - - _vm->loadLevel(level, sub); - - if (_vm->_dialogueField) - _vm->restoreAfterDialogueSequence(); - } - - _vm->moveParty(_vm->_currentBlock); - - _vm->_sceneUpdateRequired = true; - _vm->gui_drawAllCharPortraitsWithStats(); - debugPrintf("Success.\n\n"); - - } else { - debugPrintf("Syntax: set_position , , \n"); - debugPrintf(" (Warning: The sub level and block position parameters will not be checked. Invalid parameters may cause problems.)\n\n"); - } - return true; -} - -bool Debugger_EoB::cmdPrintMap(int, const char **) { - const uint8 illusion1 = _vm->gameFlags().gameID == GI_EOB1 ? 67 : 46; - const uint8 illusion2 = _vm->gameFlags().gameID == GI_EOB1 ? 64 : 46; - const uint8 plate1 = _vm->gameFlags().gameID == GI_EOB1 ? 28 : 35; - const uint8 plate2 = _vm->gameFlags().gameID == GI_EOB1 ? 28 : 36; - const uint8 hole = _vm->gameFlags().gameID == GI_EOB1 ? 27 : 38; - const uint8 stairsUp = 23; - const uint8 stairsDown = 24; - const uint8 types[] = { _vm->_teleporterWallId, illusion1, illusion2, stairsUp, stairsDown, hole, plate1, plate2 }; - const uint8 signs[] = { 1, 15, 15, 'U', 'D', 164, 'O', 'O' }; - - for (int i = 0; i < 1024; ++i) { - if (!(i % 0x20)) - debugPrintf("\n"); - LevelBlockProperty *bl = &_vm->_levelBlockProperties[i]; - uint8 f = _vm->_wllWallFlags[bl->walls[0]] | _vm->_wllWallFlags[bl->walls[1]] | _vm->_wllWallFlags[bl->walls[2]] | _vm->_wllWallFlags[bl->walls[3]]; - uint8 s = _vm->_specialWallTypes[bl->walls[0]] | _vm->_specialWallTypes[bl->walls[1]] | _vm->_specialWallTypes[bl->walls[2]] | _vm->_specialWallTypes[bl->walls[3]]; - uint8 c = ' '; - if (s == 3 || s == 4) - c = '/'; - else if (s == 2 || s == 8) - c = (uint8)'°'; - else if (f & 8) - c = 216; - else if (f & 1) - c = 2; - - if (_vm->_currentBlock == i) { - c = 'X'; - } else { - for (int ii = 0; ii < ARRAYSIZE(types); ++ii) { - if (bl->walls[0] == types[ii] || bl->walls[1] == types[ii] || bl->walls[2] == types[ii] || bl->walls[3] == types[ii]) { - c = signs[ii]; - break; - } - } - } - - debugPrintf("%c", c); - } - debugPrintf("\n\nParty Position: %c Door: %c Stairs Up/Down: %c/%c Plate: %c Hole: %c\nSwitch: %c Clickable Object: %c Illusion Wall: %c Teleporter: %c\n\n", 'X', 216, 'U', 'D', 'O', 164, '/', '°', 15, 1); - - return true; -} - -bool Debugger_EoB::cmdOpenDoor(int, const char **) { - uint16 block = _vm->calcNewBlockPosition(_vm->_currentBlock, _vm->_currentDirection); - uint8 v = _vm->_wllWallFlags[_vm->_levelBlockProperties[block].walls[0]] | _vm->_wllWallFlags[_vm->_levelBlockProperties[block].walls[1]]; - int flg = (_vm->_flags.gameID == GI_EOB1) ? 1 : 0x10; - if (!(v & 8)) { - debugPrintf("Couldn't open any door. Make sure you're facing the door you wish to open and standing right in front of it.\n\n"); - } else if (v & flg) { - debugPrintf("The door seems to be already open.\n\n"); - } else { - _vm->openDoor(block); - debugPrintf("Trying to open door at block %d.\n\n", block); - } - return true; -} - -bool Debugger_EoB::cmdCloseDoor(int, const char **) { - uint16 block = _vm->calcNewBlockPosition(_vm->_currentBlock, _vm->_currentDirection); - uint8 v = _vm->_wllWallFlags[_vm->_levelBlockProperties[block].walls[0]] | _vm->_wllWallFlags[_vm->_levelBlockProperties[block].walls[1]]; - if (!(v & 8)) { - debugPrintf("Couldn't close any door. Make sure you're facing the door you wish to close and standing right in front of it.\n\n"); - } else if ((_vm->_flags.gameID == GI_EOB1 && !(v & 1)) || (_vm->_flags.gameID == GI_EOB2 && (v & 0x20))) { - debugPrintf("The door seems to be already closed.\n\n"); - } else { - _vm->closeDoor(block); - debugPrintf("Trying to close door at block %d.\n\n", block); - } - return true; -} - -bool Debugger_EoB::cmdListFlags(int, const char **) { - debugPrintf("Flag Status\n----------------------\n\n"); - for (int i = 0; i < 32; i++) { - uint32 flag = 1 << i; - debugPrintf("%.2d %s\n", i, _vm->checkScriptFlags(flag) ? "TRUE" : "FALSE"); - } - debugPrintf("\n"); - return true; -} - -bool Debugger_EoB::cmdSetFlag(int argc, const char **argv) { - if (argc != 2) { - debugPrintf("Syntax: set_flag \n\n"); - return true; - } - - int flag = atoi(argv[1]); - if (flag < 0 || flag > 31) { - debugPrintf(" must be a value from 0 to 31.\n\n"); - } else { - _vm->setScriptFlags(1 << flag); - debugPrintf("Flag '%.2d' has been set.\n\n", flag); - } - - return true; -} - -bool Debugger_EoB::cmdClearFlag(int argc, const char **argv) { - if (argc != 2) { - debugPrintf("Syntax: clear_flag \n\n"); - return true; - } - - int flag = atoi(argv[1]); - if (flag < 0 || flag > 31) { - debugPrintf(" must be a value from 0 to 31.\n\n"); - } else { - _vm->clearScriptFlags(1 << flag); - debugPrintf("Flag '%.2d' has been cleared.\n\n", flag); - } - - return true; -} - -#endif // ENABLE_EOB - -} // End of namespace Kyra diff --git a/engines/kyra/debugger.h b/engines/kyra/debugger.h deleted file mode 100644 index 86df8ec1e6..0000000000 --- a/engines/kyra/debugger.h +++ /dev/null @@ -1,138 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef KYRA_DEBUGGER_H -#define KYRA_DEBUGGER_H - -#include "gui/debugger.h" - -namespace Kyra { - -class KyraEngine_v1; -class KyraEngine_LoK; -class KyraEngine_v2; -class KyraEngine_HoF; - -class Debugger : public ::GUI::Debugger { -public: - Debugger(KyraEngine_v1 *vm); - virtual ~Debugger() {} // we need this for __SYMBIAN32__ archaic gcc/UIQ - - virtual void initialize(); - -protected: - KyraEngine_v1 *_vm; - - bool cmdSetScreenDebug(int argc, const char **argv); - bool cmdLoadPalette(int argc, const char **argv); - bool cmdShowFacings(int argc, const char **argv); - bool cmdGameSpeed(int argc, const char **argv); - bool cmdListFlags(int argc, const char **argv); - bool cmdToggleFlag(int argc, const char **argv); - bool cmdQueryFlag(int argc, const char **argv); - bool cmdListTimers(int argc, const char **argv); - bool cmdSetTimerCountdown(int argc, const char **argv); -}; - -class Debugger_LoK : public Debugger { -public: - Debugger_LoK(KyraEngine_LoK *vm); - virtual ~Debugger_LoK() {} // we need this for __SYMBIAN32__ archaic gcc/UIQ - - virtual void initialize(); -protected: - KyraEngine_LoK *_vm; - - bool cmdEnterRoom(int argc, const char **argv); - bool cmdListScenes(int argc, const char **argv); - bool cmdGiveItem(int argc, const char **argv); - bool cmdListBirthstones(int argc, const char **argv); -}; - -class Debugger_v2 : public Debugger { -public: - Debugger_v2(KyraEngine_v2 *vm); - virtual ~Debugger_v2() {} - - virtual void initialize(); -protected: - KyraEngine_v2 *_vm; - - bool cmdEnterScene(int argc, const char **argv); - bool cmdListScenes(int argc, const char **argv); - bool cmdSceneInfo(int argc, const char **argv); - bool cmdCharacterInfo(int argc, const char **argv); - bool cmdSceneToFacing(int argc, const char **argv); - bool cmdGiveItem(int argc, const char **argv); -}; - -class Debugger_HoF : public Debugger_v2 { -public: - Debugger_HoF(KyraEngine_HoF *vm); - - virtual void initialize(); -protected: - KyraEngine_HoF *_vm; - - bool cmdPasscodes(int argc, const char **argv); -}; - -#ifdef ENABLE_LOL -class LoLEngine; - -class Debugger_LoL : public Debugger { -public: - Debugger_LoL(LoLEngine *vm); - -protected: - LoLEngine *_vm; -}; -#endif // ENABLE_LOL - -#ifdef ENABLE_EOB -class EoBCoreEngine; - -class Debugger_EoB : public Debugger { -public: - Debugger_EoB(EoBCoreEngine *vm); - - virtual void initialize(); -protected: - EoBCoreEngine *_vm; - - bool cmdImportSaveFile(int argc, const char **argv); - bool cmdSaveOriginal(int argc, const char **argv); - bool cmdListMonsters(int argc, const char **argv); - bool cmdShowPosition(int argc, const char **argv); - bool cmdSetPosition(int argc, const char **argv); - bool cmdPrintMap(int argc, const char **argv); - bool cmdOpenDoor(int argc, const char **argv); - bool cmdCloseDoor(int argc, const char **argv); - bool cmdListFlags(int argc, const char **argv); - bool cmdSetFlag(int argc, const char **argv); - bool cmdClearFlag(int argc, const char **argv); -}; -#endif // ENABLE_EOB - -} // End of namespace Kyra - -#endif diff --git a/engines/kyra/detection.cpp b/engines/kyra/detection.cpp index 0c345cc52a..a26c52ac12 100644 --- a/engines/kyra/detection.cpp +++ b/engines/kyra/detection.cpp @@ -20,12 +20,12 @@ * */ -#include "kyra/kyra_lok.h" -#include "kyra/lol.h" -#include "kyra/kyra_hof.h" -#include "kyra/kyra_mr.h" -#include "kyra/eob.h" -#include "kyra/darkmoon.h" +#include "kyra/engine/kyra_lok.h" +#include "kyra/engine/lol.h" +#include "kyra/engine/kyra_hof.h" +#include "kyra/engine/kyra_mr.h" +#include "kyra/engine/eob.h" +#include "kyra/engine/darkmoon.h" #include "common/config-manager.h" #include "common/system.h" diff --git a/engines/kyra/engine/chargen.cpp b/engines/kyra/engine/chargen.cpp new file mode 100644 index 0000000000..c080acc130 --- /dev/null +++ b/engines/kyra/engine/chargen.cpp @@ -0,0 +1,1982 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound_intern.h" + +#include "common/savefile.h" +#include "common/str-array.h" + +#include "common/config-manager.h" +#include "base/plugins.h" +#include "engines/metaengine.h" +#include "engines/game.h" + +namespace Kyra { + +// Character Generator + +class CharacterGenerator { +public: + CharacterGenerator(EoBCoreEngine *vm, Screen_EoB *screen); + ~CharacterGenerator(); + + bool start(EoBCharacter *characters, uint8 ***faceShapes); + +private: + void init(); + void initButtonsFromList(int first, int numButtons); + void initButton(int index, int x, int y, int w, int h, int keyCode); + void checkForCompleteParty(); + void toggleSpecialButton(int index, int bodyCustom, int pageNum); + void processSpecialButton(int index); + int viewDeleteCharacter(); + void createPartyMember(); + int raceSexMenu(); + int classMenu(int raceSex); + int alignmentMenu(int cClass); + int getInput(Button *buttonList); + void updateMagicShapes(); + void generateStats(int index); + void modifyMenu(); + void statsAndFacesMenu(); + void faceSelectMenu(); + int getNextFreeFaceShape(int shpIndex, int charSex, int step, int8 *selectedPortraits); + void processFaceMenuSelection(int index); + void printStats(int index, int mode); + void processNameInput(int index, int textColor); + int rollDice(); + int modifyStat(int index, int8 *stat1, int8 *stat2); + int getMaxHp(int cclass, int constitution, int level1, int level2, int level3); + int getMinHp(int cclass, int constitution, int level1, int level2, int level3); + void finish(); + + uint8 **_chargenMagicShapes; + uint8 *_chargenButtonLabels[17]; + int _activeBox; + int _magicShapesBox; + int _updateBoxShapesIndex; + int _lastUpdateBoxShapesIndex; + uint32 _chargenMagicShapeTimer; + int8 _chargenSelectedPortraits[4]; + int8 _chargenSelectedPortraits2[4]; + + uint16 _chargenMinStats[7]; + uint16 _chargenMaxStats[7]; + + const char *const *_chargenStrings1; + const char *const *_chargenStrings2; + const char *const *_chargenStatStrings; + const char *const *_chargenRaceSexStrings; + const char *const *_chargenClassStrings; + const char *const *_chargenAlignmentStrings; + const char *const *_chargenEnterGameStrings; + + const uint8 *_chargenStartLevels; + const uint8 *_chargenClassMinStats; + const uint8 *_chargenRaceMinStats; + const uint16 *_chargenRaceMaxStats; + + const EoBChargenButtonDef *_chargenButtonDefs; + + static const EoBChargenButtonDef _chargenButtonDefsDOS[]; + static const uint16 _chargenButtonKeyCodesFMTOWNS[]; + static const CreatePartyModButton _chargenModButtons[]; + static const EoBRect8 _chargenButtonBodyCoords[]; + static const int16 _chargenBoxX[]; + static const int16 _chargenBoxY[]; + static const int16 _chargenNameFieldX[]; + static const int16 _chargenNameFieldY[]; + + static const int32 _classMenuMasks[]; + static const int32 _alignmentMenuMasks[]; + + static const int16 _raceModifiers[]; + + EoBCharacter *_characters; + uint8 **_faceShapes; + + EoBCoreEngine *_vm; + Screen_EoB *_screen; +}; + +CharacterGenerator::CharacterGenerator(EoBCoreEngine *vm, Screen_EoB *screen) : _vm(vm), _screen(screen), + _characters(0), _faceShapes(0), _chargenMagicShapes(0), _chargenMagicShapeTimer(0), + _updateBoxShapesIndex(0), _lastUpdateBoxShapesIndex(0), _magicShapesBox(6), _activeBox(0) { + + _chargenStatStrings = _vm->_chargenStatStrings; + _chargenRaceSexStrings = _vm->_chargenRaceSexStrings; + _chargenClassStrings = _vm->_chargenClassStrings; + _chargenAlignmentStrings = _vm->_chargenAlignmentStrings; + + memset(_chargenSelectedPortraits, -1, sizeof(_chargenSelectedPortraits)); + memset(_chargenSelectedPortraits2, 0, sizeof(_chargenSelectedPortraits2)); + memset(_chargenMinStats, 0, sizeof(_chargenMinStats)); + memset(_chargenMaxStats, 0, sizeof(_chargenMaxStats)); + + int temp; + _chargenStrings1 = _vm->staticres()->loadStrings(kEoBBaseChargenStrings1, temp); + _chargenStrings2 = _vm->staticres()->loadStrings(kEoBBaseChargenStrings2, temp); + _chargenStartLevels = _vm->staticres()->loadRawData(kEoBBaseChargenStartLevels, temp); + _chargenEnterGameStrings = _vm->staticres()->loadStrings(kEoBBaseChargenEnterGameStrings, temp); + _chargenClassMinStats = _vm->staticres()->loadRawData(kEoBBaseChargenClassMinStats, temp); + _chargenRaceMinStats = _vm->staticres()->loadRawData(kEoBBaseChargenRaceMinStats, temp); + _chargenRaceMaxStats = _vm->staticres()->loadRawDataBe16(kEoBBaseChargenRaceMaxStats, temp); + + EoBChargenButtonDef *chargenButtonDefs = new EoBChargenButtonDef[41]; + memcpy(chargenButtonDefs, _chargenButtonDefsDOS, 41 * sizeof(EoBChargenButtonDef)); + + if (_vm->gameFlags().platform == Common::kPlatformFMTowns) { + const uint16 *c = _chargenButtonKeyCodesFMTOWNS; + for (int i = 0; i < 41; ++i) { + if (chargenButtonDefs[i].keyCode) + chargenButtonDefs[i].keyCode = *c++; + } + } + + _chargenButtonDefs = chargenButtonDefs; +} + +CharacterGenerator::~CharacterGenerator() { + if (_chargenMagicShapes) { + for (int i = 0; i < 10; i++) + delete[] _chargenMagicShapes[i]; + delete[] _chargenMagicShapes; + } + + for (int i = 0; i < 17; i++) + delete[] _chargenButtonLabels[i]; + + delete[] _chargenButtonDefs; + + _screen->clearPage(2); +} + +bool CharacterGenerator::start(EoBCharacter *characters, uint8 ***faceShapes) { + if (!characters || !faceShapes) { + warning("CharacterGenerator::start: Called without character data"); + return true; + } + + _characters = characters; + _faceShapes = *faceShapes; + + _vm->snd_stopSound(); + _vm->delay(_vm->_tickLength); + + init(); + + _screen->setScreenDim(2); + + checkForCompleteParty(); + initButtonsFromList(0, 5); + + _vm->snd_playSong(_vm->game() == GI_EOB1 ? 20 : 13); + _activeBox = 0; + + for (bool loop = true; loop && (!_vm->shouldQuit());) { + _vm->_gui->updateBoxFrameHighLight(_activeBox + 6); + int inputFlag = getInput(_vm->_activeButtons); + _vm->removeInputTop(); + + if (inputFlag) { + if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT]) { + _activeBox ^= 1; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP] || inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN]) { + _activeBox ^= 2; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE]) { + // Unlike the original we allow returning to the main menu + _vm->snd_stopSound(); + return false; + } + _vm->_gui->updateBoxFrameHighLight(-1); + } + + if (inputFlag & 0x8000) { + inputFlag = (inputFlag & 0x0F) - 1; + if (inputFlag == 4) { + loop = false; + } else { + _activeBox = inputFlag; + inputFlag = _vm->_keyMap[Common::KEYCODE_RETURN]; + } + } + + if (inputFlag == _vm->_keyMap[Common::KEYCODE_RETURN] || inputFlag == _vm->_keyMap[Common::KEYCODE_SPACE] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP5]) { + _vm->_gui->updateBoxFrameHighLight(-1); + if (_characters[_activeBox].name[0]) { + int b = _activeBox; + if (viewDeleteCharacter()) + loop = false; + if (b != _activeBox && !_characters[_activeBox].name[0]) + createPartyMember(); + } else { + createPartyMember(); + } + + initButtonsFromList(0, 5); + checkForCompleteParty(); + } + + if (loop == false) { + for (int i = 0; i < 4; i++) { + if (!_characters[i].name[0]) + loop = true; + } + } + } + + if (!_vm->shouldQuit()) { + processSpecialButton(15); + finish(); + } + + if (_vm->game() == GI_EOB2) + _vm->snd_fadeOut(); + + *faceShapes = _faceShapes; + return true; +} + +void CharacterGenerator::init() { + /*_screen->loadEoBBitmap("MENU", 0, 3, 3, 2); + Common::SeekableReadStream *s = _res->createReadStream("facedat.dmp"); + _screen->loadFileDataToPage(s, 2, 64000);*/ + + _screen->loadShapeSetBitmap("CHARGENA", 3, 3); + if (_faceShapes) { + for (int i = 0; i < 44; i++) + delete[] _faceShapes[i]; + delete[] _faceShapes; + } + + _faceShapes = new uint8*[44]; + for (int i = 0; i < 44; i++) + _faceShapes[i] = _screen->encodeShape((i % 10) << 2, (i / 10) << 5, 4, 32, true, _vm->_cgaMappingDefault); + _screen->_curPage = 0; + + _screen->loadEoBBitmap("CHARGEN", _vm->_cgaMappingDefault, 3, 3, 0); + _screen->loadShapeSetBitmap("CHARGENB", 3, 3); + if (_chargenMagicShapes) { + for (int i = 0; i < 10; i++) + delete[] _chargenMagicShapes[i]; + delete[] _chargenMagicShapes; + } + + _chargenMagicShapes = new uint8*[10]; + for (int i = 0; i < 10; i++) + _chargenMagicShapes[i] = _screen->encodeShape(i << 2, 0, 4, 32, true, _vm->_cgaMappingDefault); + + for (int i = 0; i < 17; i++) { + const CreatePartyModButton *c = &_chargenModButtons[i]; + _chargenButtonLabels[i] = c->labelW ? _screen->encodeShape(c->encodeLabelX, c->encodeLabelY, c->labelW, c->labelH, true, _vm->_cgaMappingDefault) : 0; + } + + _screen->convertPage(3, 2, _vm->_cgaMappingDefault); + _screen->_curPage = 0; + _screen->convertToHiColor(2); + _screen->shadeRect(142, 63, 306, 193, 4); + _screen->copyRegion(144, 64, 0, 0, 180, 128, 0, 2, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); +} + +void CharacterGenerator::initButtonsFromList(int first, int numButtons) { + _vm->gui_resetButtonList(); + + for (int i = 0; i < numButtons; i++) { + const EoBChargenButtonDef *e = &_chargenButtonDefs[first + i]; + initButton(i, e->x, e->y, e->w, e->h, e->keyCode); + } + + _vm->gui_notifyButtonListChanged(); +} + +void CharacterGenerator::initButton(int index, int x, int y, int w, int h, int keyCode) { + Button *b = 0; + int cnt = 1; + + if (_vm->_activeButtons) { + Button *n = _vm->_activeButtons; + while (n->nextButton) { + ++cnt; + n = n->nextButton; + } + + ++cnt; + b = n->nextButton = &_vm->_activeButtonData[cnt]; + } else { + b = &_vm->_activeButtonData[0]; + _vm->_activeButtons = b; + } + + *b = Button(); + b->flags = 0x1100; + b->data0Val2 = 12; + b->data1Val2 = b->data2Val2 = 15; + b->data3Val2 = 8; + + b->index = index + 1; + b->x = x << 3; + b->y = y; + b->width = w; + b->height = h; + b->keyCode = keyCode; + b->keyCode2 = keyCode | 0x100; +} + +void CharacterGenerator::checkForCompleteParty() { + _screen->copyRegion(0, 0, 160, 0, 160, 128, 2, 2, Screen::CR_NO_P_CHECK); + int cp = _screen->setCurPage(2); + int x = (_vm->gameFlags().platform == Common::kPlatformFMTowns) ? 184 : 168; + _screen->printShadedText(_chargenStrings1[8], x, 16, 15, 0); + _screen->setCurPage(cp); + _screen->copyRegion(160, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + + int numChars = 0; + for (int i = 0; i < 4; i++) { + if (_characters[i].name[0]) + numChars++; + } + + if (numChars == 4) { + _screen->setCurPage(2); + _screen->printShadedText(_chargenStrings1[0], x, 61, 15, 0); + _screen->setCurPage(0); + _screen->copyRegion(168, 61, 152, 125, 136, 40, 2, 0, Screen::CR_NO_P_CHECK); + toggleSpecialButton(15, 0, 0); + } else { + toggleSpecialButton(14, 0, 0); + } + + _screen->updateScreen(); +} + +void CharacterGenerator::toggleSpecialButton(int index, int bodyCustom, int pageNum) { + if (index >= 17) + return; + + const CreatePartyModButton *c = &_chargenModButtons[index]; + const EoBRect8 *p = &_chargenButtonBodyCoords[c->bodyIndex + bodyCustom]; + + int x2 = 20; + int y2 = 0; + + if (pageNum) { + x2 = c->destX + 2; + y2 = c->destY - 64; + } + + _screen->copyRegion(p->x << 3, p->y, x2 << 3, y2, p->w << 3, p->h, 2, 2, Screen::CR_NO_P_CHECK); + if (c->labelW) + _screen->drawShape(2, _chargenButtonLabels[index], (x2 << 3) + c->labelX, y2 + c->labelY, 0); + + if (pageNum == 2) + return; + + _screen->copyRegion(160, 0, c->destX << 3, c->destY, p->w << 3, p->h, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); +} + +void CharacterGenerator::processSpecialButton(int index) { + toggleSpecialButton(index, 1, 0); + _vm->snd_playSoundEffect(76); + _vm->_system->delayMillis(80); + toggleSpecialButton(index, 0, 0); +} + +int CharacterGenerator::viewDeleteCharacter() { + initButtonsFromList(0, 7); + _vm->removeInputTop(); + + _vm->_gui->updateBoxFrameHighLight(-1); + printStats(_activeBox, 2); + + int res = 0; + for (bool loop = true; loop && _characters[_activeBox].name[0] && !_vm->shouldQuit();) { + _vm->_gui->updateBoxFrameHighLight(_activeBox + 6); + int inputFlag = getInput(_vm->_activeButtons); + int cbx = _activeBox; + _vm->removeInputTop(); + + if (inputFlag == _vm->_keyMap[Common::KEYCODE_RETURN] || inputFlag == _vm->_keyMap[Common::KEYCODE_SPACE] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP5] || inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE]) { + processSpecialButton(9); + res = 0; + loop = false; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT]) { + cbx ^= 1; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP] || inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN]) { + cbx ^= 2; + } + + if (inputFlag & 0x8000) { + inputFlag = (inputFlag & 0x0F) - 1; + if (inputFlag == 4) { + res = 1; + loop = false; + } else if (inputFlag == 5) { + processSpecialButton(9); + res = 0; + loop = false; + } else if (inputFlag == 6) { + if (_characters[_activeBox].name[0]) { + processSpecialButton(16); + _characters[_activeBox].name[0] = 0; + processNameInput(_activeBox, 12); + processFaceMenuSelection(_activeBox + 50); + } + } else { + cbx = inputFlag; + } + } + + if (loop == false) + _vm->_gui->updateBoxFrameHighLight(-1); + + if (!_characters[cbx].name[0]) + loop = false; + + if (cbx != _activeBox) { + _activeBox = cbx; + _vm->_gui->updateBoxFrameHighLight(-1); + if (loop) + printStats(_activeBox, 2); + } + } + + return res; +} + +void CharacterGenerator::createPartyMember() { + _screen->setScreenDim(2); + assert(_vm->_gui); + + for (int i = 0; i != 3 && !_vm->shouldQuit(); i++) { + bool bck = false; + + switch (i) { + case 0: + _characters[_activeBox].raceSex = raceSexMenu(); + break; + case 1: + _characters[_activeBox].cClass = classMenu(_characters[_activeBox].raceSex); + if (_characters[_activeBox].cClass == _vm->_keyMap[Common::KEYCODE_ESCAPE]) + bck = true; + break; + case 2: + _characters[_activeBox].alignment = alignmentMenu(_characters[_activeBox].cClass); + if (_characters[_activeBox].alignment == _vm->_keyMap[Common::KEYCODE_ESCAPE]) + bck = true; + break; + default: + break; + } + + if (bck) + i -= 2; + }; + + if (!_vm->shouldQuit()) { + generateStats(_activeBox); + statsAndFacesMenu(); + + for (_characters[_activeBox].name[0] = 0; _characters[_activeBox].name[0] == 0 && !_vm->shouldQuit();) { + processFaceMenuSelection(_chargenMinStats[6]); + printStats(_activeBox, 0); + _screen->printShadedText(_chargenStrings2[11], 149, 100, 9, 0); + if (!_vm->shouldQuit()) { + _vm->_gui->getTextInput(_characters[_activeBox].name, 24, 100, 10, 15, 0, 8); + processNameInput(_activeBox, 2); + } + } + } +} + +int CharacterGenerator::raceSexMenu() { + _screen->drawBox(_chargenBoxX[_activeBox], _chargenBoxY[_activeBox], _chargenBoxX[_activeBox] + 32, _chargenBoxY[_activeBox] + 33, 12); + _screen->copyRegion(0, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(_chargenStrings2[8], 147, 67, 9, 0); + _vm->removeInputTop(); + + _vm->_gui->simpleMenu_setup(1, 0, _chargenRaceSexStrings, -1, 0, 0); + int16 res = -1; + + while (res == -1 && !_vm->shouldQuit()) { + res = _vm->_gui->simpleMenu_process(1, _chargenRaceSexStrings, 0, -1, 0); + updateMagicShapes(); + } + + return res; +} + +int CharacterGenerator::classMenu(int raceSex) { + int32 itemsMask = -1; + + for (int i = 0; i < 4; i++) { + // check for evil characters + if (_characters[i].name[0] && _characters[i].alignment > 5) + itemsMask = 0xFFFB; + } + + _vm->removeInputTop(); + updateMagicShapes(); + + _screen->copyRegion(0, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(_chargenStrings2[9], 147, 67, 9, 0); + + toggleSpecialButton(5, 0, 0); + + itemsMask &= _classMenuMasks[raceSex / 2]; + _vm->_gui->simpleMenu_setup(2, 15, _chargenClassStrings, itemsMask, 0, 0); + + _vm->_mouseX = _vm->_mouseY = 0; + int16 res = -1; + + while (res == -1 && !_vm->shouldQuit()) { + updateMagicShapes(); + int in = getInput(0) & 0xFF; + Common::Point mp = _vm->getMousePos(); + + if (in == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_b]) { + res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; + } else if (_vm->posWithinRect(mp.x, mp.y, 264, 171, 303, 187)) { + if (in == 199 || in == 201) + res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; + else + _vm->removeInputTop(); + } else { + res = _vm->_gui->simpleMenu_process(2, _chargenClassStrings, 0, itemsMask, 0); + } + } + + _vm->removeInputTop(); + + if (res == _vm->_keyMap[Common::KEYCODE_ESCAPE]) + processSpecialButton(5); + + return res; +} + +int CharacterGenerator::alignmentMenu(int cClass) { + int32 itemsMask = -1; + + for (int i = 0; i < 4; i++) { + // check for paladins + if (_characters[i].name[0] && _characters[i].cClass == 2) + itemsMask = 0xFE3F; + } + + _vm->removeInputTop(); + updateMagicShapes(); + + _screen->copyRegion(0, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(_chargenStrings2[10], 147, 67, 9, 0); + + toggleSpecialButton(5, 0, 0); + + itemsMask &= _alignmentMenuMasks[cClass]; + _vm->_gui->simpleMenu_setup(3, 9, _chargenAlignmentStrings, itemsMask, 0, 0); + + _vm->_mouseX = _vm->_mouseY = 0; + int16 res = -1; + + while (res == -1 && !_vm->shouldQuit()) { + updateMagicShapes(); + int in = getInput(0) & 0xFF; + Common::Point mp = _vm->getMousePos(); + + if (in == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_ESCAPE] || _vm->_gui->_menuLastInFlags == _vm->_keyMap[Common::KEYCODE_b]) { + res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; + } else if (_vm->posWithinRect(mp.x, mp.y, 264, 171, 303, 187)) { + if (in == 199 || in == 201) + res = _vm->_keyMap[Common::KEYCODE_ESCAPE]; + else + _vm->removeInputTop(); + } else { + res = _vm->_gui->simpleMenu_process(3, _chargenAlignmentStrings, 0, itemsMask, 0); + } + } + + _vm->removeInputTop(); + + if (res == _vm->_keyMap[Common::KEYCODE_ESCAPE]) + processSpecialButton(5); + + return res; +} + +int CharacterGenerator::getInput(Button *buttonList) { + if (_vm->game() == GI_EOB1 && _vm->sound()->checkTrigger()) { + _vm->sound()->resetTrigger(); + _vm->snd_playSong(20); + } else if (_vm->game() == GI_EOB2 && !_vm->sound()->isPlaying()) { + // WORKAROUND for EOB II: The original implements the same sound trigger check as in EOB I. + // However, Westwood seems to have forgotten to set the trigger at the end of the AdLib song, + // so that the music will not loop. We simply check whether the sound driver is still playing. + _vm->delay(3 * _vm->_tickLength); + _vm->snd_playSong(13); + } + return _vm->checkInput(buttonList, false, 0); +} + +void CharacterGenerator::updateMagicShapes() { + if (_magicShapesBox != _activeBox) { + _chargenMagicShapeTimer = 0; + _magicShapesBox = _activeBox; + } + + if (_chargenMagicShapeTimer < _vm->_system->getMillis()) { + if (++_updateBoxShapesIndex > 9) + _updateBoxShapesIndex = 0; + _chargenMagicShapeTimer = _vm->_system->getMillis() + 2 * _vm->_tickLength; + } + + if (_updateBoxShapesIndex == _lastUpdateBoxShapesIndex) + return; + + _screen->copyRegion(_activeBox << 5, 128, 288, 128, 32, 32, 2, 2, Screen::CR_NO_P_CHECK); + _screen->drawShape(2, _chargenMagicShapes[_updateBoxShapesIndex], 288, 128, 0); + _screen->copyRegion(288, 128, _chargenBoxX[_activeBox], _chargenBoxY[_activeBox] + 1, 32, 32, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + _lastUpdateBoxShapesIndex = _updateBoxShapesIndex; +} + +void CharacterGenerator::generateStats(int index) { + EoBCharacter *c = &_characters[index]; + + for (int i = 0; i < 3; i++) { + c->level[i] = _chargenStartLevels[(c->cClass << 2) + i]; + c->experience[i] = (_vm->game() == GI_EOB2 ? 69000 : 5000) / _chargenStartLevels[(c->cClass << 2) + 3]; + } + + int rc = c->raceSex >> 1; + for (int i = 0; i < 6; i++) { + _chargenMinStats[i] = MAX(_chargenClassMinStats[c->cClass * 6 + i], _chargenRaceMinStats[rc * 6 + i]); + _chargenMaxStats[i] = _chargenRaceMaxStats[rc * 6 + i]; + } + + if (_vm->_charClassModifier[c->cClass]) + _chargenMaxStats[0] = 18; + + uint16 sv[6]; + for (int i = 0; i < 6; i++) { + sv[i] = MAX(rollDice() + _raceModifiers[rc * 6 + i], _chargenMinStats[i]); + if (!i && sv[i] == 18) + sv[i] |= (uint16)(_vm->rollDice(1, 100) << 8); + if (sv[i] > _chargenMaxStats[i]) + sv[i] = _chargenMaxStats[i]; + } + + c->strengthCur = c->strengthMax = sv[0] & 0xFF; + c->strengthExtCur = c->strengthExtMax = sv[0] >> 8; + c->intelligenceCur = c->intelligenceMax = sv[1] & 0xFF; + c->wisdomCur = c->wisdomMax = sv[2] & 0xFF; + c->dexterityCur = c->dexterityMax = sv[3] & 0xFF; + c->constitutionCur = c->constitutionMax = sv[4] & 0xFF; + c->charismaCur = c->charismaMax = sv[5] & 0xFF; + c->armorClass = 10 + _vm->getDexterityArmorClassModifier(sv[3] & 0xFF); + c->hitPointsCur = 0; + + for (int l = 0; l < 3; l++) { + for (int i = 0; i < c->level[l]; i++) + c->hitPointsCur += _vm->generateCharacterHitpointsByLevel(index, 1 << l); + } + + c->hitPointsMax = c->hitPointsCur; +} + +void CharacterGenerator::modifyMenu() { + _vm->removeInputTop(); + printStats(_activeBox, 3); + + EoBCharacter *c = &_characters[_activeBox]; + int8 hpLO = c->hitPointsCur; + + for (int i = 0; i >= 0 && i < 7;) { + switch (i) { + case 0: + i = modifyStat(i, &c->strengthCur, &c->strengthExtCur); + break; + case 1: + i = modifyStat(i, &c->intelligenceCur, 0); + break; + case 2: + i = modifyStat(i, &c->wisdomCur, 0); + break; + case 3: + i = modifyStat(i, &c->dexterityCur, 0); + break; + case 4: + i = modifyStat(i, &c->constitutionCur, 0); + break; + case 5: + i = modifyStat(i, &c->charismaCur, 0); + break; + case 6: + hpLO = c->hitPointsCur; + i = modifyStat(i, &hpLO, 0); + c->hitPointsCur = hpLO; + break; + default: + break; + } + + if (i == -2 || _vm->shouldQuit()) + break; + else if (i < 0) + i = 6; + i %= 7; + + printStats(_activeBox, 3); + } + + printStats(_activeBox, 1); +} + +void CharacterGenerator::statsAndFacesMenu() { + faceSelectMenu(); + printStats(_activeBox, 1); + initButtonsFromList(27, 4); + _vm->removeInputTop(); + int in = 0; + + while (!in && !_vm->shouldQuit()) { + updateMagicShapes(); + in = getInput(_vm->_activeButtons); + _vm->removeInputTop(); + + if (in == 0x8001) { + processSpecialButton(4); + updateMagicShapes(); + generateStats(_activeBox); + in = -1; + } else if (in == 0x8002) { + processSpecialButton(7); + modifyMenu(); + in = -1; + } else if (in == 0x8003) { + processSpecialButton(8); + faceSelectMenu(); + in = -1; + } else if (in == 0x8004 || in == _vm->_keyMap[Common::KEYCODE_KP5]) { + processSpecialButton(6); + in = 1; + } else { + in = 0; + } + + if (in & 0x8000) { + printStats(_activeBox, 1); + initButtonsFromList(27, 4); + in = 0; + } + } + + _vm->_gui->updateBoxFrameHighLight(6 + _activeBox); + _vm->_gui->updateBoxFrameHighLight(-1); +} + +void CharacterGenerator::faceSelectMenu() { + int8 sp[4]; + memcpy(sp, _chargenSelectedPortraits2, sizeof(sp)); + _vm->removeInputTop(); + initButtonsFromList(21, 6); + + int charSex = _characters[_activeBox].raceSex % 2; + int8 shp = charSex ? 26 : 0; + + printStats(_activeBox, 4); + toggleSpecialButton(12, 0, 0); + toggleSpecialButton(13, 0, 0); + _vm->_gui->updateBoxFrameHighLight(-1); + + shp = getNextFreeFaceShape(shp, charSex, 1, _chargenSelectedPortraits); + + int res = -1; + int box = 1; + + while (res == -1 && !_vm->shouldQuit()) { + int8 shpOld = shp; + + for (int i = 0; i < 4; i++) { + sp[i] = shp; + _screen->drawShape(0, _faceShapes[sp[i]], 176 + (i << 5), 66, 0); + shp = getNextFreeFaceShape(shp + 1, charSex, 1, _chargenSelectedPortraits); + } + + shp = shpOld; + int in = 0; + + while (!in && !_vm->shouldQuit()) { + updateMagicShapes(); + in = getInput(_vm->_activeButtons); + _vm->removeInputTop(); + + _vm->_gui->updateBoxFrameHighLight(box + 10); + + if (in == 0x8002 || in == _vm->_keyMap[Common::KEYCODE_RIGHT]) { + processSpecialButton(13); + in = 2; + } else if (in > 0x8002 && in < 0x8007) { + box = (in & 7) - 3; + in = 3; + } else if (in == 0x8001 || in == _vm->_keyMap[Common::KEYCODE_LEFT]) { + processSpecialButton(12); + in = 1; + } else if (in == _vm->_keyMap[Common::KEYCODE_RETURN] || in == _vm->_keyMap[Common::KEYCODE_KP5]) { + in = 3; + } else if (in & 0x8000) { + in &= 0xFF; + } else { + in = 0; + } + } + + _vm->_gui->updateBoxFrameHighLight(-1); + + if (in == 1) + shp = getNextFreeFaceShape(shp - 1, charSex, -1, _chargenSelectedPortraits); + else if (in == 2) + shp = getNextFreeFaceShape(shp + 1, charSex, 1, _chargenSelectedPortraits); + else if (in == 3) + res = box; + } + + if (!_vm->shouldQuit()) { + _vm->_gui->updateBoxFrameHighLight(-1); + updateMagicShapes(); + + _chargenSelectedPortraits[_activeBox] = sp[res]; + _characters[_activeBox].portrait = sp[res]; + _characters[_activeBox].faceShape = _faceShapes[sp[res]]; + + printStats(_activeBox, 1); + } +} + +int CharacterGenerator::getNextFreeFaceShape(int shpIndex, int charSex, int step, int8 *selectedPortraits) { + int shpCur = ((shpIndex < 0) ? 43 : shpIndex) % 44; + bool notUsable = false; + + do { + notUsable = false; + for (int i = 0; i < 4; i++) { + if (_characters[i].name[0] && selectedPortraits[i] == shpCur) + notUsable = true; + } + + if ((charSex && (shpCur < 26)) || (!charSex && (shpCur > 28))) + notUsable = true; + + if (notUsable) { + shpCur += step; + shpCur = ((shpCur < 0) ? 43 : shpCur) % 44; + } + } while (notUsable); + + return shpCur; +} + +void CharacterGenerator::processFaceMenuSelection(int index) { + _vm->_gui->updateBoxFrameHighLight(-1); + if (index <= 48) + _screen->drawShape(0, _characters[_activeBox].faceShape, _chargenBoxX[_activeBox], _chargenBoxY[_activeBox] + 1, 0); + else + toggleSpecialButton(index - 50, 0, 0); +} + +void CharacterGenerator::printStats(int index, int mode) { + _screen->copyRegion(0, 0, 160, 0, 160, 128, 2, 2, Screen::CR_NO_P_CHECK); + _screen->_curPage = 2; + + EoBCharacter *c = &_characters[index]; + + if (mode != 4) + _screen->drawShape(2, c->faceShape, 224, 2, 0); + + _screen->printShadedText(c->name, 160 + ((160 - _screen->getTextWidth(c->name)) / 2), 35, 15, 0); + _screen->printShadedText(_chargenRaceSexStrings[c->raceSex], 160 + ((20 - strlen(_chargenRaceSexStrings[c->raceSex])) << 2), 45, 15, 0); + _screen->printShadedText(_chargenClassStrings[c->cClass], 160 + ((20 - strlen(_chargenClassStrings[c->cClass])) << 2), 54, 15, 0); + + for (int i = 0; i < 6; i++) + _screen->printShadedText(_chargenStatStrings[i], 163, (i + 8) << 3, 15, 0); + + _screen->printShadedText(_chargenStrings1[2], 248, 64, 15, 0); + + Common::String str = Common::String::format(_chargenStrings1[3], _vm->getCharStrength(c->strengthCur, c->strengthExtCur).c_str(), c->intelligenceCur, c->wisdomCur, c->dexterityCur, c->constitutionCur, c->charismaCur); + _screen->printShadedText(str.c_str(), 192, 64, 15, 0); + + str = Common::String::format(_chargenStrings1[4], c->armorClass, c->hitPointsMax); + _screen->printShadedText(str.c_str(), 280, 64, 15, 0); + + const char *lvlStr = c->level[2] ? _chargenStrings1[7] : (c->level[1] ? _chargenStrings1[6] : _chargenStrings1[5]); + str = Common::String::format(lvlStr, c->level[0], c->level[1], c->level[2]); + _screen->printShadedText(str.c_str(), 280, 80, 15, 0); + + switch (mode) { + case 1: + toggleSpecialButton(4, 0, 2); + toggleSpecialButton(7, 0, 2); + toggleSpecialButton(8, 0, 2); + toggleSpecialButton(6, 0, 2); + break; + + case 2: + toggleSpecialButton(16, 0, 2); + toggleSpecialButton(9, 0, 2); + break; + + case 3: + toggleSpecialButton(10, 0, 2); + toggleSpecialButton(11, 0, 2); + toggleSpecialButton(9, 0, 2); + break; + + default: + break; + } + + _screen->copyRegion(160, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + + if (mode != 3) + _screen->updateScreen(); + + _screen->_curPage = 0; +} + +void CharacterGenerator::processNameInput(int index, int textColor) { + Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); + _screen->fillRect(_chargenNameFieldX[index], _chargenNameFieldY[index], _chargenNameFieldX[index] + 59, _chargenNameFieldY[index] + 5, 12); + int xOffs = (60 - _screen->getTextWidth(_characters[index].name)) >> 1; + _screen->printText(_characters[index].name, _chargenNameFieldX[index] + xOffs, _chargenNameFieldY[index], textColor, 0); + _screen->updateScreen(); + _screen->setFont(of); +} + +int CharacterGenerator::rollDice() { + int res = 0; + int min = 10; + + for (int i = 0; i < 4; i++) { + int d = _vm->rollDice(1, 6, 0); + res += d; + if (d < min) + min = d; + } + + res -= min; + return res; +} + +int CharacterGenerator::modifyStat(int index, int8 *stat1, int8 *stat2) { + uint8 *s1 = (uint8 *)stat1; + uint8 *s2 = (uint8 *)stat2; + + initButtonsFromList(31, 10); + Button *b = _vm->gui_getButton(_vm->_activeButtons, index + 1); + + printStats(_activeBox, 3); + _vm->removeInputTop(); + + Common::String statStr = index ? Common::String::format("%d", *s1) : _vm->getCharStrength(*s1, *s2); + + _screen->copyRegion(b->x - 112, b->y - 64, b->x + 32, b->y, 40, b->height, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(statStr.c_str(), b->x + 32, b->y, 6, 0); + _screen->updateScreen(); + + EoBCharacter *c = &_characters[_activeBox]; + + int ci = index; + uint8 v2 = s2 ? *s2 : 0; + + if (index == 6) { + _chargenMaxStats[6] = getMaxHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); + _chargenMinStats[6] = getMinHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); + } + + for (bool loop = true; loop && !_vm->shouldQuit();) { + uint8 v1 = *s1; + updateMagicShapes(); + int inputFlag = getInput(_vm->_activeButtons); + _vm->removeInputTop(); + + if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP4] || inputFlag == _vm->_keyMap[Common::KEYCODE_MINUS] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP_MINUS] || inputFlag == 0x8009) { + processSpecialButton(11); + v1--; + + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP6] || inputFlag == _vm->_keyMap[Common::KEYCODE_PLUS] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP_PLUS] || inputFlag == 0x8008) { + processSpecialButton(10); + v1++; + + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP8]) { + ci = (ci - 1) % 7; + loop = false; + + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP2]) { + ci = (ci + 1) % 7; + loop = false; + + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_o] || inputFlag == _vm->_keyMap[Common::KEYCODE_KP5] || inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE] || inputFlag == 0x800A) { + processSpecialButton(9); + loop = false; + ci = -2; + + } else if (inputFlag & 0x8000) { + inputFlag = (inputFlag & 0x0F) - 1; + if (index != inputFlag) { + ci = inputFlag; + loop = false; + } + } + + if (v1 == *s1) + continue; + + if (!index) { + while (v1 > 18) { + v1--; + v2++; + } + while (v2 > 0 && v1 < 18) { + v1++; + v2--; + } + + v1 = CLIP(v1, _chargenMinStats[index], _chargenMaxStats[index] & 0xFF); + v2 = (v1 == 18 && _chargenMaxStats[index] >= 19) ? CLIP(v2, 0, 100) : 0; + if (s2) + *s2 = v2; + else + error("CharacterGenerator::modifyStat:..."); + } else { + v1 = CLIP(v1, _chargenMinStats[index], _chargenMaxStats[index]); + } + + *s1 = v1; + + if (index == 6) + _characters[_activeBox].hitPointsMax = v1; + + statStr = index ? Common::String::format("%d", *s1) : _vm->getCharStrength(*s1, *s2); + + _screen->copyRegion(b->x - 112, b->y - 64, b->x + 32, b->y, 40, b->height, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(statStr.c_str(), b->x + 32, b->y, 6, 0); + _screen->updateScreen(); + + if (index == 4) { + int oldVal = c->hitPointsCur; + _chargenMaxStats[6] = getMaxHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); + _chargenMinStats[6] = getMinHp(c->cClass, c->constitutionCur, c->level[0], c->level[1], c->level[2]); + c->hitPointsMax = c->hitPointsCur = CLIP(c->hitPointsCur, _chargenMinStats[6], _chargenMaxStats[6]); + + if (c->hitPointsCur != oldVal) { + statStr = Common::String::format("%d", c->hitPointsCur); + _screen->copyRegion(120, 72, 264, 136, 40, 8, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(statStr.c_str(), 264, 136, 15, 0); + _screen->updateScreen(); + } + + } else if (index == 3) { + int oldVal = c->armorClass; + c->armorClass = _vm->getDexterityArmorClassModifier(v1) + 10; + + if (c->armorClass != oldVal) { + statStr = Common::String::format("%d", c->armorClass); + _screen->copyRegion(120, 64, 264, 128, 40, 8, 2, 0, Screen::CR_NO_P_CHECK); + _screen->printShadedText(statStr.c_str(), 264, 128, 15, 0); + _screen->updateScreen(); + } + } + + if (loop == false) { + statStr = index ? Common::String::format("%d", *s1) : _vm->getCharStrength(*s1, *s2); + _screen->printText(statStr.c_str(), b->x + 32, b->y, 15, 0); + _screen->updateScreen(); + } + } + + return ci; +} + +int CharacterGenerator::getMaxHp(int cclass, int constitution, int level1, int level2, int level3) { + int res = 0; + constitution = _vm->getClassAndConstHitpointsModifier(cclass, constitution); + + int m = _vm->getCharacterClassType(cclass, 0); + if (m != -1) + res = _vm->getModifiedHpLimits(m, constitution, level1, false); + + m = _vm->getCharacterClassType(cclass, 1); + if (m != -1) + res += _vm->getModifiedHpLimits(m, constitution, level2, false); + + m = _vm->getCharacterClassType(cclass, 2); + if (m != -1) + res += _vm->getModifiedHpLimits(m, constitution, level3, false); + + res /= _vm->_numLevelsPerClass[cclass]; + + return res; +} + +int CharacterGenerator::getMinHp(int cclass, int constitution, int level1, int level2, int level3) { + int res = 0; + constitution = _vm->getClassAndConstHitpointsModifier(cclass, constitution); + + int m = _vm->getCharacterClassType(cclass, 0); + if (m != -1) + res = _vm->getModifiedHpLimits(m, constitution, level1, true); + + m = _vm->getCharacterClassType(cclass, 1); + if (m != -1) + res += _vm->getModifiedHpLimits(m, constitution, level2, true); + + m = _vm->getCharacterClassType(cclass, 2); + if (m != -1) + res += _vm->getModifiedHpLimits(m, constitution, level3, true); + + res /= _vm->_numLevelsPerClass[cclass]; + + return res; +} + +void CharacterGenerator::finish() { + _screen->copyRegion(0, 0, 160, 0, 160, 128, 2, 2, Screen::CR_NO_P_CHECK); + int cp = _screen->setCurPage(2); + _screen->printShadedText(_chargenEnterGameStrings[0], (_vm->gameFlags().platform == Common::kPlatformFMTowns) ? 184 : 168, 32, 15, 0); + _screen->setCurPage(cp); + _screen->copyRegion(160, 0, 144, 64, 160, 128, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + if (_vm->game() == GI_EOB1) { + static const int8 classDefaultItemsList[] = { + 1, 17, 2, 17, 46, -1, 4, -1, 5, -1, 6, + 2, 7, -1, 8, -1, 9, 21, 10, 2, 31, 2 + }; + + static const int8 classDefaultItemsListIndex[] = { + 4, 8, 0, -1, 4, 3, 0, -1, 4, 10, + 0, 8, 3, 6, 1, -1, 2, 7, 0, -1, + 4, 5, 0, -1, 4, 7, 0, 8, 4, 5, + 0, 8, 4, 6, 8, 8, 4, 6, 5, 8, + 3, 6, 5, -1, 2, 7, 5, 0, 4, 6, + 7, 0, 4, 3, 7, 0, 2, 6, 7, 1 + }; + + _characters[0].inventory[2] = _vm->duplicateItem(35); + + for (int i = 0; i < 4; i++) { + EoBCharacter *c = &_characters[i]; + c->flags = 1; + c->food = 100; + c->id = i; + c->inventory[3] = _vm->duplicateItem(10); + + for (int ii = 0; ii < 4; ii++) { + int l = classDefaultItemsListIndex[(c->cClass << 2) + ii] << 1; + if (classDefaultItemsList[l] == -1) + continue; + + int d = classDefaultItemsList[l]; + int slot = classDefaultItemsList[l + 1]; + + if (slot < 0) { + slot = 0; + if (c->inventory[slot]) + slot++; + if (c->inventory[slot]) + slot++; + } + + if (slot != 2 && c->inventory[slot]) + continue; + + if (d == 5 && (c->raceSex >> 1) == 3) + d = 36; + + if (slot == 2) { + while (c->inventory[slot]) + slot++; + } + + c->inventory[slot] = _vm->duplicateItem(d); + } + + _vm->recalcArmorClass(i); + } + + } else { + static const uint8 classDefaultItemsListIndex[] = { 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 3, 2, 0, 0, 2 }; + static const int8 itemList0[] = { 3, 36, 0, 17, -1, 0, 0, 56, 1, 17, 31, 0, 1, 23, 1, 17, 31, 1, 1 }; + static const int8 itemList1[] = { 1, 2, 0, 17, -1, 0, 0 }; + static const int8 itemList2[] = { 2, 56, 1, 17, 31, 0, 1, 23, 1, 17, 31, 0, 1 }; + static const int8 itemList3[] = { 2, 1, 1, 17, 31, 1, 1, 1, 0, 17, 31, 2, 1 }; + static const int8 *const itemList[] = { itemList0, itemList1, itemList2, itemList3 }; + + for (int i = 0; i < 4; i++) { + EoBCharacter *c = &_characters[i]; + c->flags = 1; + c->food = 100; + c->id = i; + const int8 *df = itemList[classDefaultItemsListIndex[c->cClass]]; + int v1 = _vm->rollDice(1, *df++, -1); + + df = &df[v1 * 6]; + for (int ii = 0; ii < 2; ii++) { + if (df[0] == -1) + break; + _vm->createInventoryItem(c, df[0], df[1], df[2]); + df = &df[3]; + } + + uint16 m = _vm->_classModifierFlags[c->cClass]; + v1 = _vm->rollDice(1, 2, -1); + + int ival = 0; + int itype = 0; + + if (m & 0x31) { + if ((c->raceSex >> 1) == 3) { + itype = 22; + ival = 1; + } else { + if (v1 == 0) { + itype = 5; + ival = 1; + } else { + itype = 34; + } + } + } else if (m & 0x04) { + itype = 26; + if (v1 != 0) + ival = 1; + } else if (m & 0x08) { + ival = 1; + itype = ((c->raceSex >> 1) == 3) ? 22 : 5; + } else { + if (v1 == 0) { + itype = 3; + } else { + itype = 4; + ival = 1; + } + } + + _vm->createInventoryItem(c, itype, ival, 0); + _vm->createInventoryItem(c, 10, -1, 2); + _vm->createInventoryItem(c, 10, -1, 2); + _vm->createInventoryItem(c, 24, 2, 2); + + if (_vm->_classModifierFlags[c->cClass] & 2) { + _vm->createInventoryItem(c, 7, -1, 1); + _vm->createInventoryItem(c, 21, 4, 2); + _vm->createInventoryItem(c, 21, 13, 2); + } + + if (_vm->_classModifierFlags[c->cClass] & 0x14) { + if (c->cClass == 2) + _vm->createInventoryItem(c, 27, -1, 1); + else + _vm->createInventoryItem(c, 8, -1, 1); + + _vm->createInventoryItem(c, 20, 49, 1); + } + + if (_vm->_classModifierFlags[c->cClass] & 8) + _vm->createInventoryItem(c, 6, -1, 1); + + if (i == 0) + _vm->createInventoryItem(c, 93, -1, 2); + + _vm->recalcArmorClass(i); + } + } + + for (int i = 0; i < 4; i++) { + if (_vm->_classModifierFlags[_characters[i].cClass] & 2) + _characters[i].mageSpellsAvailableFlags = (_vm->game() == GI_EOB2) ? 0x81CB6 : 0x26C; + + if (_vm->_classModifierFlags[_characters[i].cClass] & 0x14 && _vm->game() == GI_EOB2) { + // Cleric/Paladin: Add Turn Undead spell + _characters[i].clericSpells[0] = 29; + } + } + + for (int i = 0; i < 4; i++) { + EoBCharacter *c = &_characters[i]; + c->strengthMax = c->strengthCur; + c->strengthExtMax = c->strengthExtCur; + c->intelligenceMax = c->intelligenceCur; + c->wisdomMax = c->wisdomCur; + c->dexterityMax = c->dexterityCur; + c->constitutionMax = c->constitutionCur; + c->charismaMax = c->charismaCur; + c->hitPointsMax = c->hitPointsCur; + } + + _vm->gui_resetButtonList(); + + if (_faceShapes) { + for (int i = 0; i < 44; i++) { + bool del = true; + for (int ii = 0; ii < 4; ii++) { + if (_characters[ii].faceShape == _faceShapes[i]) + del = false; + } + if (del) + delete[] _faceShapes[i]; + } + delete[] _faceShapes; + _faceShapes = 0; + } + + if (_chargenMagicShapes) { + for (int i = 0; i < 10; i++) + delete[] _chargenMagicShapes[i]; + delete[] _chargenMagicShapes; + _chargenMagicShapes = 0; + } + + for (int i = 0; i < 17; i++) { + delete[] _chargenButtonLabels[i]; + _chargenButtonLabels[i] = 0; + } +} + +const EoBChargenButtonDef CharacterGenerator::_chargenButtonDefsDOS[] = { + { 0x01, 0x37, 0x31, 0x32, 0x70 }, + { 0x09, 0x37, 0x31, 0x32, 0x71 }, + { 0x01, 0x77, 0x31, 0x32, 0x72 }, + { 0x09, 0x77, 0x31, 0x32, 0x73 }, + { 0x03, 0xB5, 0x53, 0x10, 0x1A }, + { 0x21, 0xAC, 0x26, 0x10, 0x19 }, + { 0x1C, 0xAC, 0x26, 0x10, 0x21 }, + { 0x21, 0xAC, 0x26, 0x10, 0x32 }, + { 0x13, 0x50, 0x9A, 0x08, 0x00 }, + { 0x13, 0x58, 0x9A, 0x08, 0x00 }, + { 0x13, 0x60, 0x9A, 0x08, 0x00 }, + { 0x13, 0x68, 0x9A, 0x08, 0x00 }, + { 0x13, 0x70, 0x9A, 0x08, 0x00 }, + { 0x13, 0x78, 0x9A, 0x08, 0x00 }, + { 0x13, 0x80, 0x9A, 0x08, 0x00 }, + { 0x13, 0x88, 0x9A, 0x08, 0x00 }, + { 0x13, 0x90, 0x9A, 0x08, 0x00 }, + { 0x13, 0x98, 0x9A, 0x08, 0x00 }, + { 0x13, 0xA0, 0x9A, 0x08, 0x00 }, + { 0x13, 0xA8, 0x9A, 0x08, 0x00 }, + { 0x13, 0xB0, 0x9A, 0x08, 0x00 }, + { 0x12, 0x42, 0x20, 0x10, 0x00 }, + { 0x12, 0x52, 0x20, 0x10, 0x00 }, + { 0x16, 0x42, 0x20, 0x20, 0x00 }, + { 0x1A, 0x42, 0x20, 0x20, 0x00 }, + { 0x1E, 0x42, 0x20, 0x20, 0x00 }, + { 0x22, 0x42, 0x20, 0x20, 0x00 }, + { 0x1C, 0x9C, 0x26, 0x10, 0x14 }, + { 0x21, 0x9C, 0x26, 0x10, 0x34 }, + { 0x1C, 0xAC, 0x26, 0x10, 0x22 }, + { 0x21, 0xAC, 0x26, 0x10, 0x26 }, + { 0x12, 0x80, 0x35, 0x08, 0x00 }, + { 0x12, 0x88, 0x35, 0x08, 0x00 }, + { 0x12, 0x90, 0x35, 0x08, 0x00 }, + { 0x12, 0x98, 0x35, 0x08, 0x00 }, + { 0x12, 0xA0, 0x35, 0x08, 0x00 }, + { 0x12, 0xA8, 0x35, 0x08, 0x00 }, + { 0x1D, 0x88, 0x35, 0x08, 0x00 }, + { 0x1B, 0xAC, 0x15, 0x10, 0x0D }, + { 0x1E, 0xAC, 0x15, 0x10, 0x0C }, + { 0x21, 0xAC, 0x25, 0x10, 0x19 } +}; + +const uint16 CharacterGenerator::_chargenButtonKeyCodesFMTOWNS[] = { + 93, 94, 95, 96, 80, 79, 68, 66, 82, 77, 70, 75, 43, 45, 79 +}; + +const CreatePartyModButton CharacterGenerator::_chargenModButtons[] = { + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x40 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x80 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0A, 0x80 }, + { 0x00, 0xC0, 0x04, 0x05, 0x07, 0x05, 0x08, 0x1C, 0x9C }, + { 0x04, 0xC0, 0x03, 0x05, 0x0A, 0x05, 0x08, 0x21, 0xAC }, + { 0x07, 0xC0, 0x03, 0x05, 0x0B, 0x05, 0x08, 0x21, 0xAC }, + { 0x0A, 0xC0, 0x04, 0x05, 0x06, 0x05, 0x08, 0x21, 0x9C }, + { 0x18, 0xC0, 0x03, 0x05, 0x09, 0x05, 0x08, 0x1C, 0xAC }, + { 0x0E, 0xC0, 0x02, 0x05, 0x0F, 0x05, 0x08, 0x21, 0xAC }, + { 0x10, 0xC0, 0x01, 0x05, 0x09, 0x05, 0x04, 0x1B, 0xAC }, + { 0x11, 0xC0, 0x01, 0x01, 0x09, 0x07, 0x04, 0x1E, 0xAC }, + { 0x12, 0xC0, 0x03, 0x07, 0x07, 0x04, 0x06, 0x12, 0x42 }, + { 0x15, 0xC0, 0x03, 0x07, 0x07, 0x04, 0x06, 0x12, 0x52 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x03, 0xB5 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x03, 0xB5 }, + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x1C, 0xAC } +}; + +const EoBRect8 CharacterGenerator::_chargenButtonBodyCoords[] = { + { 0x00, 0x80, 0x04, 0x20 }, + { 0x04, 0x80, 0x04, 0x20 }, + { 0x08, 0x80, 0x04, 0x20 }, + { 0x0C, 0x80, 0x04, 0x20 }, + { 0x0E, 0xA0, 0x03, 0x10 }, + { 0x0B, 0xA0, 0x03, 0x10 }, + { 0x10, 0x80, 0x04, 0x10 }, + { 0x10, 0x90, 0x04, 0x10 }, + { 0x11, 0xA0, 0x05, 0x10 }, + { 0x11, 0xB0, 0x05, 0x10 }, + { 0x16, 0xA0, 0x05, 0x10 }, + { 0x16, 0xB0, 0x05, 0x10 }, + { 0x00, 0xA0, 0x0B, 0x10 }, + { 0x14, 0x80, 0x0B, 0x10 }, + { 0x14, 0x90, 0x0B, 0x10 } +}; + +const int16 CharacterGenerator::_chargenBoxX[] = { 0x10, 0x50, 0x10, 0x50 }; +const int16 CharacterGenerator::_chargenBoxY[] = { 0x3F, 0x3F, 0x7F, 0x7F }; +const int16 CharacterGenerator::_chargenNameFieldX[] = { 0x02, 0x42, 0x02, 0x42 }; +const int16 CharacterGenerator::_chargenNameFieldY[] = { 0x6B, 0x6B, 0xAB, 0xAB }; + +const int32 CharacterGenerator::_classMenuMasks[] = { + 0x003F, 0x07BB, 0x77FB, 0x00F1, 0x08F1, 0x00B1 +}; + +const int32 CharacterGenerator::_alignmentMenuMasks[] = { + 0x01FF, 0x0007, 0x0001, 0x01FF, 0x01FF, 0x01FE, 0x01FF, 0x01FE, + 0x01FF, 0x01FE, 0x01FE, 0x01FE, 0x01FF, 0x0007, 0x01FF +}; + +const int16 CharacterGenerator::_raceModifiers[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, -1, 0, 1, -1, 0, 0, 0, -1, 0, 0, 1, 0, 0 +}; + +// Transfer Party + +class TransferPartyWiz { +public: + TransferPartyWiz(EoBCoreEngine *vm, Screen_EoB *screen); + ~TransferPartyWiz(); + + bool start(); + +private: + bool selectAndLoadTransferFile(); + bool transferFileDialogue(Common::String &dest); + + + int selectCharactersMenu(); + void drawCharPortraitWithStats(int charIndex, bool enabled); + void updateHighlight(int index); + + void convertStats(); + void convertInventory(); + Item convertItem(Item eob1Item); + void giveKhelbensCoin(); + + EoBCoreEngine *_vm; + Screen_EoB *_screen; + + int _highlight; + EoBItem *_oldItems; + + const uint16 *_portraitFrames; + const uint8 *_convertTable; + const uint8 *_itemTable; + const uint32 *_expTable; + const char *const *_strings1; + const char *const *_strings2; + const char *const *_labels; +}; + +TransferPartyWiz::TransferPartyWiz(EoBCoreEngine *vm, Screen_EoB *screen) : _vm(vm), _screen(screen) { + int temp; + _portraitFrames = _vm->staticres()->loadRawDataBe16(kEoB2TransferPortraitFrames, temp); + _convertTable = _vm->staticres()->loadRawData(kEoB2TransferConvertTable, temp); + _itemTable = _vm->staticres()->loadRawData(kEoB2TransferItemTable, temp); + _expTable = _vm->staticres()->loadRawDataBe32(kEoB2TransferExpTable, temp); + _strings1 = _vm->staticres()->loadStrings(kEoB2TransferStrings1, temp); + _strings2 = _vm->staticres()->loadStrings(kEoB2TransferStrings2, temp); + _labels = _vm->staticres()->loadStrings(kEoB2TransferLabels, temp); + _highlight = -1; + _oldItems = 0; +} + +TransferPartyWiz::~TransferPartyWiz() { + delete[] _oldItems; +} + +bool TransferPartyWiz::start() { + _screen->copyPage(0, 12); + + if (!selectAndLoadTransferFile()) + return false; + + convertStats(); + + _oldItems = new EoBItem[600]; + memcpy(_oldItems, _vm->_items, sizeof(EoBItem) * 600); + _vm->loadItemDefs(); + + int selection = selectCharactersMenu(); + if (selection == 0) { + for (int i = 0; i < 6; i++) + delete[] _vm->_characters[i].faceShape; + memset(_vm->_characters, 0, sizeof(EoBCharacter) * 6); + return false; + } + + int ch = 0; + for (int i = 0; i < 6; i++) { + if (selection & (1 << i)) { + if (ch != i) { + delete[] _vm->_characters[ch].faceShape; + memcpy(&_vm->_characters[ch], &_vm->_characters[i], sizeof(EoBCharacter)); + _vm->_characters[i].faceShape = 0; + } + ch++; + } + } + memset(&_vm->_characters[4], 0, sizeof(EoBCharacter) * 2); + + convertInventory(); + giveKhelbensCoin(); + + return true; +} + +bool TransferPartyWiz::selectAndLoadTransferFile() { + do { + _screen->copyPage(12, 0); + if (transferFileDialogue(_vm->_savegameFilename)) + break; + } while (_vm->_gui->confirmDialogue2(15, 68, 1)); + + if (_vm->_savegameFilename.empty()) + return false; + + if (_vm->loadGameState(-1).getCode() != Common::kNoError) + return false; + + return true; +} + + bool TransferPartyWiz::transferFileDialogue(Common::String &dest) { + _vm->_gui->transferWaitBox(); + + Common::Array eobTargets; + const Common::ConfigManager::DomainMap dom = ConfMan.getGameDomains(); + + for (Common::ConfigManager::DomainMap::const_iterator i = dom.begin(); i != dom.end(); ++i) { + if (ConfMan.get("gameid", i->_key).equals("eob")) + eobTargets.push_back(i->_key); + _vm->updateInput(); + } + + if (eobTargets.empty()) + return false; + + Common::String target = _vm->_gui->transferTargetMenu(eobTargets); + _screen->copyPage(12, 0); + + if (target.empty()) + return true; + + dest = target + ".fin"; + Common::InSaveFile *in = _vm->_saveFileMan->openForLoading(dest); + if (in) { + delete in; + if (_vm->_gui->confirmDialogue2(15, -2, 1)) + return true; + } + + _screen->copyPage(12, 0); + + bool result = _vm->_gui->transferFileMenu(target, dest); + _screen->copyPage(12, 0); + + return result; +} + +int TransferPartyWiz::selectCharactersMenu() { + _screen->setCurPage(2); + _screen->setFont(Screen::FID_6_FNT); + _screen->clearCurPage(); + + _vm->gui_drawBox(0, 0, 320, 163, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); + _screen->printText(_strings2[0], 5, 3, 15, 0); + _screen->printText(_strings2[1], 5, 10, 15, 0); + + for (int i = 0; i < 6; i++) + drawCharPortraitWithStats(i, 0); + + _vm->gui_drawBox(4, 148, 43, 12, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); + _vm->gui_drawBox(272, 148, 43, 12, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); + + _screen->printShadedText(_labels[0], 9, 151, 15, 0); + _screen->printShadedText(_labels[1], 288, 151, 15, 0); + + _screen->setCurPage(0); + _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + int selection = 0; + int highlight = 0; + bool update = false; + + for (bool loop = true; loop && (!_vm->shouldQuit());) { + int inputFlag = _vm->checkInput(0, false, 0) & 0x8FF; + _vm->removeInputTop(); + + if (inputFlag) { + if (inputFlag == _vm->_keyMap[Common::KEYCODE_LEFT] || inputFlag == _vm->_keyMap[Common::KEYCODE_RIGHT]) { + highlight ^= 1; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_UP]) { + highlight -= 2; + if (highlight < 0) + highlight += 8; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_DOWN]) { + highlight += 2; + if (highlight >= 8) + highlight -= 8; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_RETURN] || inputFlag == _vm->_keyMap[Common::KEYCODE_SPACE]) { + update = true; + } else if (inputFlag == _vm->_keyMap[Common::KEYCODE_ESCAPE]) { + update = true; + highlight = 6; + } else if (inputFlag == 199) { + for (int i = 0; i < 8; i++) { + int t = i << 2; + if (_vm->posWithinRect(_vm->_mouseX, _vm->_mouseY, _portraitFrames[t], _portraitFrames[t + 1], _portraitFrames[t + 2], _portraitFrames[t + 3])) { + highlight = i; + update = true; + break; + } + } + } + } + + updateHighlight(highlight); + + if (!update) + continue; + + update = false; + + if (highlight < 6) { + if (_vm->_characters[highlight].flags & 1) { + selection ^= (1 << highlight); + drawCharPortraitWithStats(highlight, (selection & (1 << highlight)) ? true : false); + _screen->updateScreen(); + } + continue; + } + + int x = (highlight - 6) * 268 + 4; + _vm->gui_drawBox(x, 148, 43, 12, _vm->guiSettings()->colors.fill, _vm->guiSettings()->colors.fill, -1); + _screen->updateScreen(); + _vm->_system->delayMillis(80); + _vm->gui_drawBox(x, 148, 43, 12, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, -1); + _screen->updateScreen(); + + if (highlight == 6 || _vm->shouldQuit()) { + _screen->setFont(Screen::FID_8_FNT); + return 0; + } + + int count = 0; + for (int i = 0; i < 6; i++) { + if (selection & (1 << i)) + count++; + } + + if (count == 4 || _vm->shouldQuit()) + loop = false; + else + _vm->_gui->messageDialogue(16, count < 4 ? 69 : 70, 6); + + _screen->updateScreen(); + } + + _screen->setFont(Screen::FID_8_FNT); + if (_vm->shouldQuit()) + return 0; + else + _vm->_gui->messageDialogue(16, 71, 6); + + return selection; +} + +void TransferPartyWiz::drawCharPortraitWithStats(int charIndex, bool enabled) { + int16 x = (charIndex % 2) * 159; + int16 y = (charIndex / 2) * 40; + EoBCharacter *c = &_vm->_characters[charIndex]; + + _screen->fillRect(x + 4, y + 24, x + 36, y + 57, 12); + _vm->gui_drawBox(x + 40, y + 24, 118, 34, _vm->guiSettings()->colors.frame1, _vm->guiSettings()->colors.frame2, _vm->guiSettings()->colors.fill); + + if (!(c->flags & 1)) + return; + + _screen->drawShape(_screen->_curPage, c->faceShape, x + 4, y + 25, 0); + + int color1 = 15; + int color2 = 12; + + if (enabled) { + color1 = 6; + color2 = 15; + } else { + _screen->drawShape(_screen->_curPage, _vm->_disabledCharGrid, x + 4, y + 25, 0); + } + + _screen->printShadedText(c->name, x + 44, y + 27, color1, 0); + _screen->printText(_vm->_chargenRaceSexStrings[c->raceSex], x + 43, y + 36, color2, 0); + _screen->printText(_vm->_chargenClassStrings[c->cClass], x + 43, y + 43, color2, 0); + + Common::String tmp = Common::String::format(_strings1[0], c->level[0]); + for (int i = 1; i < _vm->_numLevelsPerClass[c->cClass]; i++) + tmp += Common::String::format(_strings1[1], c->level[i]); + _screen->printText(tmp.c_str(), x + 43, y + 50, color2, 0); +} + +void TransferPartyWiz::updateHighlight(int index) { + static const int16 xPos[] = { 9, 288 }; + if (_highlight > 5 && _highlight != index) + _screen->printText(_labels[_highlight - 6], xPos[_highlight - 6], 151, 15, 0); + + if (index < 6) { + _vm->_gui->updateBoxFrameHighLight(14 + index); + _highlight = index; + return; + } + + if (_highlight == index) + return; + + if (_highlight < 6) + _vm->_gui->updateBoxFrameHighLight(-1); + + _screen->printText(_labels[index - 6], xPos[index - 6], 151, 6, 0); + _screen->updateScreen(); + _highlight = index; +} + +void TransferPartyWiz::convertStats() { + for (int i = 0; i < 6; i++) { + EoBCharacter *c = &_vm->_characters[i]; + uint32 aflags = 0; + + for (int ii = 0; ii < 25; ii++) { + if (c->mageSpellsAvailableFlags & (1 << ii)) { + int8 f = (int8)_convertTable[ii + 1] - 1; + if (f != -1) + aflags |= (1 << f); + } + } + c->mageSpellsAvailableFlags = aflags; + + c->armorClass = 0; + c->disabledSlots = 0; + c->flags &= 1; + c->hitPointsCur = c->hitPointsMax; + c->food = 100; + + c->effectFlags = 0; + c->damageTaken = 0; + memset(c->clericSpells, 0, sizeof(int8) * 80); + memset(c->mageSpells, 0, sizeof(int8) * 80); + memset(c->timers, 0, sizeof(uint32) * 10); + memset(c->events, 0, sizeof(int8) * 10); + memset(c->effectsRemainder, 0, sizeof(uint8) * 4); + memset(c->slotStatus, 0, sizeof(int8) * 5); + + for (int ii = 0; ii < 3; ii++) { + int t = _vm->getCharacterClassType(c->cClass, ii); + if (t == -1) + continue; + if (c->experience[ii] > _expTable[t]) + c->experience[ii] = _expTable[t]; + } + } +} + +void TransferPartyWiz::convertInventory() { + for (int i = 0; i < 4; i++) { + EoBCharacter *c = &_vm->_characters[i]; + + for (int slot = 0; slot < 27; slot++) { + Item itm = c->inventory[slot]; + if (slot == 16) { + Item first = itm; + c->inventory[slot] = 0; + + for (bool forceLoop = true; (itm && (itm != first)) || forceLoop; itm = _oldItems[itm].prev) { + forceLoop = false; + _vm->setItemPosition(&c->inventory[slot], -2, convertItem(itm), 0); + } + } else { + c->inventory[slot] = convertItem(itm); + } + } + } +} + +Item TransferPartyWiz::convertItem(Item eob1Item) { + if (!eob1Item) + return 0; + + EoBItem *itm1 = &_oldItems[eob1Item]; + + if (!_itemTable[itm1->type]) + return 0; + + Item newItem = _vm->duplicateItem(1); + EoBItem *itm2 = &_vm->_items[newItem]; + bool match = false; + + itm2->flags = itm1->flags | 0x40; + itm2->icon = itm1->icon; + itm2->type = itm1->type; + itm2->level = 0xFF; + + switch (itm2->type) { + case 35: + itm1->value += 25; + // fall through + case 34: + itm2->value = _convertTable[itm1->value]; + if (!itm2->value) { + itm2->block = -1; + return 0; + } + break; + case 39: + itm2->value = itm1->value - 1; + break; + case 48: + if (itm1->value == 5) { + memset(itm2, 0, sizeof(EoBItem)); + itm2->block = -1; + return 0; + } + itm2->value = itm1->value; + itm2->flags = ((itm1->flags & 0x3F) + 3) | 0x40; + break; + case 18: + itm2->icon = 19; + // fall through + default: + itm2->value = itm1->value; + break; + } + + switch ((_vm->_itemTypes[itm2->type].extraProperties & 0x7F) - 1) { + case 0: + case 1: + case 2: + if (itm2->value) + itm2->flags |= 0x80; + break; + case 4: + case 5: + case 8: + case 9: + case 13: + case 15: + case 17: + itm2->flags |= 0x80; + break; + default: + break; + } + + for (int i = 1; i < 600; i++) { + if (i == 60 || i == 62 || i == 63 || i == 83) + continue; + EoBItem *tmp = &_vm->_items[i]; + if (tmp->level || tmp->block == -2 || tmp->type != itm2->type || tmp->icon != itm2->icon) + continue; + itm2->nameUnid = tmp->nameUnid; + itm2->nameId = tmp->nameId; + match = true; + break; + } + + if (!match) { + for (int i = 1; i < 600; i++) { + if (i == 60 || i == 62 || i == 63 || i == 83) + continue; + EoBItem *tmp = &_vm->_items[i]; + if (tmp->level || tmp->block == -2 || tmp->type != itm2->type) + continue; + itm2->nameUnid = tmp->nameUnid; + itm2->nameId = tmp->nameId; + match = true; + break; + } + } + + if (!match) { + memset(itm2, 0, sizeof(EoBItem)); + itm2->block = -1; + return 0; + } + + itm2->level = 0; + return newItem; +} + +void TransferPartyWiz::giveKhelbensCoin() { + bool success = false; + for (int i = 0; i < 4 && !success; i++) { + EoBCharacter *c = &_vm->_characters[i]; + + for (int slot = 2; slot < 16; slot++) { + if (c->inventory[slot]) + continue; + _vm->createInventoryItem(c, 93, -1, slot); + success = true; + break; + } + } + + if (!success) { + _vm->_characters[0].inventory[2] = 0; + _vm->createInventoryItem(&_vm->_characters[0], 93, -1, 2); + } +} + +// Start functions + +bool EoBCoreEngine::startCharacterGeneration() { + return CharacterGenerator(this, _screen).start(_characters, &_faceShapes); +} + +bool EoBCoreEngine::startPartyTransfer() { + return TransferPartyWiz(this, _screen).start(); +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/darkmoon.cpp b/engines/kyra/engine/darkmoon.cpp new file mode 100644 index 0000000000..9731f00533 --- /dev/null +++ b/engines/kyra/engine/darkmoon.cpp @@ -0,0 +1,493 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/darkmoon.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound.h" + +namespace Kyra { + +DarkMoonEngine::DarkMoonEngine(OSystem *system, const GameFlags &flags) : EoBCoreEngine(system, flags) { + _dscDoorType5Offs = 0; + _numSpells = 70; + _menuChoiceInit = 4; + + _kheldranStrings = _npcStrings[0] = _npcStrings[1] = _hornStrings = 0; + _utilMenuStrings = _ascii2SjisTables = _ascii2SjisTables2 = 0; + _npcShpData = _dscDoorType5Offs = _hornSounds = 0; + _dreamSteps = 0; +} + +DarkMoonEngine::~DarkMoonEngine() { +} + +Common::Error DarkMoonEngine::init() { + Common::Error err = EoBCoreEngine::init(); + if (err.getCode() != Common::kNoError) + return err; + + initStaticResource(); + + _monsterProps = new EoBMonsterProperty[10]; + + if (_configRenderMode == Common::kRenderEGA) { + Palette pal(16); + _screen->loadPalette(_egaDefaultPalette, pal, 16); + _screen->setScreenPalette(pal); + } + + _screen->loadPalette(_flags.platform == Common::kPlatformFMTowns ? "MENU.PAL" : "PALETTE.COL", _screen->getPalette(0)); + _screen->setScreenPalette(_screen->getPalette(0)); + + // adjust menu settings for EOB II FM-Towns + if (_flags.platform == Common::kPlatformFMTowns) { + _screen->modifyScreenDim(6, 10, 100, 21, 40); + _screen->modifyScreenDim(27, 0, 0, 21, 2); + } + + return Common::kNoError; +} + +void DarkMoonEngine::startupNew() { + _currentLevel = 4; + _currentSub = 0; + loadLevel(4, 0); + _currentBlock = 171; + _currentDirection = 2; + setHandItem(0); + EoBCoreEngine::startupNew(); +} + +void DarkMoonEngine::drawNpcScene(int npcIndex) { + const uint8 *shpDef = &_npcShpData[npcIndex << 3]; + for (int i = npcIndex; i != 255; i = shpDef[7]) { + shpDef = &_npcShpData[i << 3]; + _screen->_curPage = 2; + const uint8 *shp = _screen->encodeShape(READ_LE_UINT16(shpDef), shpDef[2], shpDef[3], shpDef[4]); + _screen->_curPage = 0; + _screen->drawShape(0, shp, 88 + shpDef[5] - (shp[2] << 2), 104 + shpDef[6] - shp[1], 5); + delete[] shp; + } +} + +void DarkMoonEngine::runNpcDialogue(int npcIndex) { + if (npcIndex == 0) { + snd_playSoundEffect(57); + if (npcJoinDialogue(0, 1, 3, 2)) + setScriptFlags(0x40); + } else if (npcIndex == 1) { + snd_playSoundEffect(53); + gui_drawDialogueBox(); + + _txt->printDialogueText(4, 0); + int r = runDialogue(-1, 2, _npcStrings[0][0], _npcStrings[0][1]) - 1; + + if (r == 0) { + snd_stopSound(); + delay(3 * _tickLength); + snd_playSoundEffect(91); + npcJoinDialogue(1, 5, 6, 7); + } else if (r == 1) { + setScriptFlags(0x20); + } + + } else if (npcIndex == 2) { + snd_playSoundEffect(55); + gui_drawDialogueBox(); + + _txt->printDialogueText(8, 0); + int r = runDialogue(-1, 2, _npcStrings[1][0], _npcStrings[1][1]) - 1; + + if (r == 0) { + if (rollDice(1, 2, -1)) + _txt->printDialogueText(9, _okStrings[0]); + else + npcJoinDialogue(2, 102, 103, 104); + setScriptFlags(8); + } else if (r == 1) { + _currentDirection = 0; + } + } +} + +void DarkMoonEngine::updateUsedCharacterHandItem(int charIndex, int slot) { + EoBItem *itm = &_items[_characters[charIndex].inventory[slot]]; + if (itm->type == 48 || itm->type == 62) { + if (itm->value == 5) + return; + int charges = itm->flags & 0x3F; + if (--charges) + --itm->flags; + else + deleteInventoryItem(charIndex, slot); + } else if (itm->type == 26 || itm->type == 34 || itm->type == 35) { + deleteInventoryItem(charIndex, slot); + } +} + +void DarkMoonEngine::generateMonsterPalettes(const char *file, int16 monsterIndex) { + int cp = _screen->setCurPage(2); + _screen->loadShapeSetBitmap(file, 3, 3); + uint8 tmpPal[16]; + uint8 newPal[16]; + + for (int i = 0; i < 6; i++) { + int dci = monsterIndex + i; + memcpy(tmpPal, _monsterShapes[dci] + 4, 16); + int colx = 302 + 3 * i; + + for (int ii = 0; ii < 16; ii++) { + uint8 col = _screen->getPagePixel(_screen->_curPage, colx, 184 + ii); + int iii = 0; + for (; iii < 16; iii++) { + if (tmpPal[iii] == col) { + newPal[ii] = iii; + break; + } + } + + if (iii == 16) + newPal[ii] = 0; + } + + for (int ii = 1; ii < 3; ii++) { + memcpy(tmpPal, _monsterShapes[dci] + 4, 16); + + for (int iii = 0; iii < 16; iii++) { + uint8 col = _screen->getPagePixel(_screen->_curPage, colx + ii, 184 + iii); + if (newPal[iii]) + tmpPal[newPal[iii]] = col; + } + + int c = i; + if (monsterIndex >= 18) + c += 6; + + c = (c << 1) + (ii - 1); + assert(c < 24); + memcpy(_monsterPalettes[c], tmpPal, 16); + } + } + + _screen->setCurPage(cp); +} + +void DarkMoonEngine::loadMonsterDecoration(Common::SeekableReadStream *stream, int16 monsterIndex) { + int len = stream->readUint16LE(); + Common::List activeDecorations; + + for (int i = 0; i < len; i++) { + for (int ii = 0; ii < 6; ii++) { + uint8 dc[6]; + stream->read(dc, 6); + if (!dc[2] || !dc[3]) + continue; + + SpriteDecoration *m = &_monsterDecorations[i * 6 + ii + monsterIndex]; + if (_flags.platform != Common::kPlatformFMTowns) + m->shp = _screen->encodeShape(dc[0], dc[1], dc[2], dc[3]); + m->x = (int8)dc[4]; + m->y = (int8)dc[5]; + activeDecorations.push_back(m); + } + } + + if (_flags.platform == Common::kPlatformFMTowns) { + while (!activeDecorations.empty()) { + activeDecorations.front()->shp = loadTownsShape(stream); + activeDecorations.pop_front(); + } + } +} + +void DarkMoonEngine::replaceMonster(int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem) { + uint8 flg = _levelBlockProperties[block].flags & 7; + + if (flg == 7 || _currentBlock == block || (flg && (_monsterProps[type].u30 || pos == 4))) + return; + + for (int i = 0; i < 30; i++) { + if (_monsters[i].block != block) + continue; + if (_monsters[i].pos == 4 || _monsterProps[_monsters[i].type].u30) + return; + } + + int index = -1; + int maxDist = 0; + + for (int i = 0; i < 30; i++) { + if (_monsters[i].hitPointsCur <= 0) { + index = i; + break; + } + + if (_monsters[i].flags & 0x40) + continue; + + // WORKAROUND for bug #3611077 (Dran's dragon transformation sequence triggered prematurely): + // The boss level and the mindflayer level share the same monster data. If you hang around + // long enough in the mindflayer level all 30 monster slots will be used up. When this + // happens it will trigger the dragon transformation sequence when Dran is moved around by script. + // We avoid removing Dran here by prefering monster slots occupied by monsters from another + // sub level. + if (_monsters[i].sub != _currentSub) { + index = i; + break; + } + + int dist = getBlockDistance(_monsters[i].block, _currentBlock); + + if (dist > maxDist) { + maxDist = dist; + index = i; + } + } + + if (index == -1) + return; + + if (_monsters[index].hitPointsCur > 0) + killMonster(&_monsters[index], false); + + initMonster(index, unit, block, pos, dir, type, shpIndex, mode, h2, randItem, fixedItem); +} + +bool DarkMoonEngine::killMonsterExtra(EoBMonsterInPlay *m) { + // WORKAROUND for bug #3611077 (see DarkMoonEngine::replaceMonster()) + // The mindflayers have monster type 0, just like Dran. Using a monster slot occupied by a mindflayer would trigger the dragon transformation + // sequence when all 30 monster slots are used up. We avoid this by checking for m->sub == 1. + if (_currentLevel == 16 && _currentSub == 1 && m->sub == 1 && (_monsterProps[m->type].capsFlags & 4)) { + if (m->type) { + _playFinale = true; + _runFlag = false; + delay(850); + } else { + m->hitPointsCur = 150; + m->curRemoteWeapon = 0; + m->numRemoteAttacks = 255; + m->shpIndex++; + m->type++; + seq_dranDragonTransformation(); + } + return false; + } + return true; +} + +const uint8 *DarkMoonEngine::loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs) { + _screen->loadShapeSetBitmap(filename, 3, 3); + for (int i = 0; i < 3; i++) { + _doorShapes[doorIndex * 3 + i] = _screen->encodeShape(READ_LE_UINT16(shapeDefs), READ_LE_UINT16(shapeDefs + 2), READ_LE_UINT16(shapeDefs + 4), READ_LE_UINT16(shapeDefs + 6)); + shapeDefs += 8; + } + + for (int i = 0; i < 2; i++) { + _doorSwitches[doorIndex * 3 + i].shp = _screen->encodeShape(READ_LE_UINT16(shapeDefs), READ_LE_UINT16(shapeDefs + 2), READ_LE_UINT16(shapeDefs + 4), READ_LE_UINT16(shapeDefs + 6)); + shapeDefs += 8; + _doorSwitches[doorIndex * 3 + i].x = *shapeDefs; + shapeDefs += 2; + _doorSwitches[doorIndex * 3 + i].y = *shapeDefs; + shapeDefs += 2; + } + _screen->_curPage = 0; + return shapeDefs; +} + +void DarkMoonEngine::drawDoorIntern(int type, int, int x, int y, int w, int wall, int mDim, int16, int16) { + int shapeIndex = type * 3 + 2 - mDim; + uint8 *shp = _doorShapes[shapeIndex]; + if (!shp) + return; + + if ((_doorType[type] == 0) || (_doorType[type] == 1)) { + y = _dscDoorY1[mDim] - shp[1]; + x -= (shp[2] << 2); + + if (_doorType[type] == 1) { + drawBlockObject(0, 2, shp, x, y, 5); + shp = _doorShapes[3 + shapeIndex]; + } + + y -= ((wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult1[mDim]); + + if (_specialWallTypes[wall] == 5) + y -= _dscDoorType5Offs[shapeIndex]; + + } else if (_doorType[type] == 2) { + x -= (shp[2] << 2); + y = _dscDoorY2[mDim] - ((wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult3[mDim]); + } + + drawBlockObject(0, 2, shp, x, y, 5); + + if (_doorType[type] == 2) { + shp = _doorShapes[shapeIndex + 3]; + y = _dscDoorFrameY2[mDim] - shp[1] + (((wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult3[mDim]) >> 1) - 1; + drawBlockObject(0, 2, shp, x, y, 5); + } + + if (_wllShapeMap[wall] == -1 && !_noDoorSwitch[type]) + drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); +} + +void DarkMoonEngine::restParty_npc() { + int insalId = -1; + int numChar = 0; + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (testCharacter(i, 2) && _characters[i].portrait == -1) + insalId = i; + numChar++; + } + + if (insalId == -1 || numChar < 5) + return; + + removeCharacterFromParty(insalId); + if (insalId < 4) + exchangeCharacters(insalId, testCharacter(5, 1) ? 5 : 4); + + clearScriptFlags(6); + + if (!stripPartyItems(1, 1, 1, 1)) + stripPartyItems(2, 1, 1, 1); + stripPartyItems(31, 0, 1, 3); + stripPartyItems(39, 1, 0, 3); + stripPartyItems(47, 0, 1, 2); + + _items[createItemOnCurrentBlock(28)].value = 26; + + gui_drawPlayField(false); + gui_drawAllCharPortraitsWithStats(); + + _screen->setClearScreenDim(10); + _screen->set16bitShadingLevel(4); + gui_drawBox(_screen->_curDim->sx << 3, _screen->_curDim->sy, _screen->_curDim->w << 3, _screen->_curDim->h, guiSettings()->colors.frame1, guiSettings()->colors.frame2, -1); + gui_drawBox((_screen->_curDim->sx << 3) + 1, _screen->_curDim->sy + 1, (_screen->_curDim->w << 3) - 2, _screen->_curDim->h - 2, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + _screen->set16bitShadingLevel(0); + _gui->messageDialogue2(11, 63, 6); + _gui->messageDialogue2(11, 64, 6); +} + +bool DarkMoonEngine::restParty_extraAbortCondition() { + if (_currentLevel != 3) + return false; + + seq_nightmare(); + + return true; +} + +void DarkMoonEngine::useHorn(int charIndex, int weaponSlot) { + int v = _items[_characters[charIndex].inventory[weaponSlot]].value - 1; + _txt->printMessage(_hornStrings[v]); + snd_playSoundEffect(_hornSounds[v]); +} + +bool DarkMoonEngine::checkPartyStatusExtra() { + if (checkScriptFlags(0x100000)) + seq_kheldran(); + return _gui->confirmDialogue2(14, 67, 1); +} + +void DarkMoonEngine::drawLightningColumn() { + int f = rollDice(1, 2, -1); + int y = 0; + + for (int i = 0; i < 6; i++) { + f ^= 1; + drawBlockObject(f, 2, _lightningColumnShape, 72, y, 5); + y += 64; + } +} + +int DarkMoonEngine::resurrectionSelectDialogue() { + countResurrectionCandidates(); + + _rrNames[_rrCount] = _abortStrings[0]; + _rrId[_rrCount++] = 99; + + int r = _rrId[runDialogue(-1, 9, _rrNames[0], _rrNames[1], _rrNames[2], _rrNames[3], _rrNames[4], _rrNames[5], _rrNames[6], _rrNames[7], _rrNames[8]) - 1]; + if (r == 99) + return 0; + + if (r < 0) { + r = -r; + if (prepareForNewPartyMember(33, r)) + initNpc(r - 1); + } else { + _characters[r].hitPointsCur = 1; + } + + return 1; +} + +int DarkMoonEngine::charSelectDialogue() { + int cnt = 0; + const char *namesList[7]; + memset(namesList, 0, 7 * sizeof(const char *)); + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 3)) + continue; + namesList[cnt++] = _characters[i].name; + } + + namesList[cnt++] = _abortStrings[0]; + + int r = runDialogue(-1, 7, namesList[0], namesList[1], namesList[2], namesList[3], namesList[4], namesList[5], namesList[6]) - 1; + if (r == cnt - 1) + return 99; + + for (cnt = 0; cnt < 6; cnt++) { + if (!testCharacter(cnt, 3)) + continue; + if (--r < 0) + break; + } + return cnt; +} + +void DarkMoonEngine::characterLevelGain(int charIndex) { + EoBCharacter *c = &_characters[charIndex]; + int s = _numLevelsPerClass[c->cClass]; + for (int i = 0; i < s; i++) { + uint32 er = getRequiredExperience(c->cClass, i, c->level[i] + 1); + if (er == 0xFFFFFFFF) + continue; + + increaseCharacterExperience(charIndex, er - c->experience[i] + 1); + } +} + +const KyraRpgGUISettings *DarkMoonEngine::guiSettings() { + return (_flags.platform == Common::kPlatformFMTowns) ? &_guiSettingsFMTowns : &_guiSettingsDOS; +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/darkmoon.h b/engines/kyra/engine/darkmoon.h new file mode 100644 index 0000000000..3577bdbcec --- /dev/null +++ b/engines/kyra/engine/darkmoon.h @@ -0,0 +1,139 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_EOB + +#ifndef KYRA_EOB2_H +#define KYRA_EOB2_H + +#include "kyra/engine/eobcommon.h" + +namespace Kyra { + +class DarkmoonSequenceHelper; + +struct DarkMoonAnimCommand { + uint8 command; + uint8 obj; + int16 x1; + uint8 y1; + uint8 delay; + uint8 pal; + uint8 x2; + uint8 y2; + uint8 w; + uint8 h; +}; + +class DarkMoonEngine : public EoBCoreEngine { +friend class GUI_EoB; +friend class DarkmoonSequenceHelper; +public: + DarkMoonEngine(OSystem *system, const GameFlags &flags); + ~DarkMoonEngine(); + +private: + // Init / Release + Common::Error init(); + void initStaticResource(); + void initSpells(); + + // Main Menu + int mainMenu(); + int mainMenuLoop(); + void townsUtilitiesMenu(); + + int _menuChoiceInit; + + // Main loop + void startupNew(); + void startupLoad() {} + + // Intro/Outro + void seq_playIntro(); + void seq_playFinale(); + void seq_playCredits(DarkmoonSequenceHelper *sq, const uint8 *data, int sd, int backupPage, int tempPage, int speed); + + // Ingame sequence + void seq_nightmare(); + void seq_kheldran(); + void seq_dranDragonTransformation(); + + const int8 *_dreamSteps; + const char *const *_kheldranStrings; + + // characters + void drawNpcScene(int npcIndex); + void runNpcDialogue(int npcIndex); + + const uint8 *_npcShpData; + const char *const *_npcStrings[2]; + + // items + void updateUsedCharacterHandItem(int charIndex, int slot); + + // Monsters + void generateMonsterPalettes(const char *file, int16 monsterIndex); + void loadMonsterDecoration(Common::SeekableReadStream *stream, int16 monsterIndex); + void replaceMonster(int unit, uint16 block, int d, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem); + bool killMonsterExtra(EoBMonsterInPlay *m); + + // Level + void loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2) {} + const uint8 *loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs); + void drawDoorIntern(int type, int, int x, int y, int w, int wall, int mDim, int16, int16); + + const uint8 *_dscDoorType5Offs; + + // Fight + static const uint8 _monsterAcHitChanceTbl1[]; + static const uint8 _monsterAcHitChanceTbl2[]; + + // Rest party + void restParty_npc(); + bool restParty_extraAbortCondition(); + + // misc + void useHorn(int charIndex, int weaponSlot); + bool checkPartyStatusExtra(); + void drawLightningColumn(); + int resurrectionSelectDialogue(); + int charSelectDialogue(); + void characterLevelGain(int charIndex); + + const KyraRpgGUISettings *guiSettings(); + + const char *const *_hornStrings; + const uint8 *_hornSounds; + + const char *const *_utilMenuStrings; + + static const KyraRpgGUISettings _guiSettingsDOS; + static const KyraRpgGUISettings _guiSettingsFMTowns; + static const uint8 _egaDefaultPalette[]; +}; + +} // End of namespace Kyra + +#endif + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/eob.cpp b/engines/kyra/engine/eob.cpp new file mode 100644 index 0000000000..18ed9c623a --- /dev/null +++ b/engines/kyra/engine/eob.cpp @@ -0,0 +1,573 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eob.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound.h" + +namespace Kyra { + +EoBEngine::EoBEngine(OSystem *system, const GameFlags &flags) + : EoBCoreEngine(system, flags) { + _numSpells = 53; + _menuChoiceInit = 4; + + _turnUndeadString = 0; + _finBonusStrings = _npcStrings[1] = _npcStrings[2] = 0; + _npcStrings[3] = _npcStrings[4] = _npcStrings[5] = _npcStrings[6] = 0; + _npcStrings[7] = _npcStrings[8] = _npcStrings[9] = _npcStrings[10] = 0; + _npcShpData = _npcSubShpIndex1 = _npcSubShpIndex2 = _npcSubShpY = 0; + _dscDoorScaleMult4 = _dscDoorScaleMult5 = _dscDoorScaleMult6 = _dscDoorY3 = 0; + _dscDoorY4 = _dscDoorY5 = _dscDoorY6 = _dscDoorY7 = _doorShapeEncodeDefs = 0; + _doorSwitchShapeEncodeDefs = _doorSwitchCoords = 0; + _dscDoorCoordsExt = 0; +} + +EoBEngine::~EoBEngine() { + delete[] _itemsOverlay; +} + +Common::Error EoBEngine::init() { + Common::Error err = EoBCoreEngine::init(); + if (err.getCode() != Common::kNoError) + return err; + + initStaticResource(); + + if (_configRenderMode != Common::kRenderCGA) + _itemsOverlay = _res->fileData((_configRenderMode == Common::kRenderEGA) ? "ITEMRMP.EGA" : "ITEMRMP.VGA", 0); + + _screen->modifyScreenDim(7, 0x01, 0xB3, 0x22, 0x12); + _screen->modifyScreenDim(9, 0x01, 0x7D, 0x26, 0x3F); + _screen->modifyScreenDim(12, 0x01, 0x04, 0x14, 0xA0); + + _scriptTimersCount = 1; + + if (_configRenderMode == Common::kRenderEGA) { + Palette pal(16); + _screen->loadPalette(_egaDefaultPalette, pal, 16); + _screen->setScreenPalette(pal); + } else { + _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); + } + + return Common::kNoError; +} + +void EoBEngine::startupNew() { + _currentLevel = 1; + _currentSub = 0; + loadLevel(1, 0); + _currentBlock = 490; + _currentDirection = 0; + setHandItem(0); + + EoBCoreEngine::startupNew(); +} + +void EoBEngine::startupLoad() { + _sound->loadSoundFile("ADLIB"); +} + +void EoBEngine::drawNpcScene(int npcIndex) { + _screen->copyRegion(0, 0, 0, 0, 176, 120, 6, 0, Screen::CR_NO_P_CHECK); + switch (npcIndex) { + case 0: + encodeDrawNpcSeqShape(2, 88, 104); + break; + + case 1: + if (_npcSequenceSub == -1) { + encodeDrawNpcSeqShape(0, 88, 104); + } else { + encodeDrawNpcSeqShape(0, 60, 104); + encodeDrawNpcSeqShape(5, 116, 104); + } + break; + + case 2: + if (_npcSequenceSub == -1) { + encodeDrawNpcSeqShape(3, 88, 104); + } else { + encodeDrawNpcSeqShape(3, 60, 104); + encodeDrawNpcSeqShape(_npcSubShpIndex1[_npcSequenceSub], 116, 104); + encodeDrawNpcSeqShape(_npcSubShpIndex2[_npcSequenceSub], 116, _npcSubShpY[_npcSequenceSub]); + } + break; + + case 3: + encodeDrawNpcSeqShape(7, 88, 104); + break; + + case 4: + encodeDrawNpcSeqShape(6, 88, 104); + break; + + case 5: + encodeDrawNpcSeqShape(18, 88, 88); + break; + + case 6: + encodeDrawNpcSeqShape(17, 88, 104); + break; + + case 7: + encodeDrawNpcSeqShape(4, 88, 104); + break; + + default: + break; + } +} + +void EoBEngine::encodeDrawNpcSeqShape(int npcIndex, int drawX, int drawY) { + const uint8 *shpDef = &_npcShpData[npcIndex << 2]; + _screen->_curPage = 2; + const uint8 *shp = _screen->encodeShape(shpDef[0], shpDef[1], shpDef[2], shpDef[3], false, _cgaMappingDefault); + _screen->_curPage = 0; + _screen->drawShape(0, shp, drawX - (shp[2] << 2), drawY - shp[1], 5); + delete[] shp; +} + +#define DLG2(txt, buttonstr) (runDialogue(txt, 2, _npcStrings[buttonstr][0], _npcStrings[buttonstr][1]) - 1) +#define DLG3(txt, buttonstr) (runDialogue(txt, 3, _npcStrings[buttonstr][0], _npcStrings[buttonstr][1], _npcStrings[buttonstr][2]) - 1) +#define DLG2A3(cond, txt, buttonstr1, buttonstr2) ((cond) ? (DLG2(txt, buttonstr1) ? 2 : 0) : DLG3(txt, buttonstr2)) +#define TXT(txt) _txt->printDialogueText(txt, _moreStrings[0]) + +void EoBEngine::runNpcDialogue(int npcIndex) { + int r = 0; + int a = 0; + Item itm = 0; + + switch (npcIndex) { + case 0: + for (r = 1; r == 1;) { + gui_drawDialogueBox(); + r = DLG2A3(checkScriptFlags(0x2000), 8, 2, 1); + if (r == 1) { + TXT(1); + setScriptFlags(0x2000); + } else if (r == 0) { + npcJoinDialogue(6, 12, 23, 2); + setScriptFlags(0x4000); + } + } + break; + + case 1: + if (!checkScriptFlags(0x10000)) { + if (checkScriptFlags(0x8000)) { + a = 13; + } else { + setScriptFlags(0x8000); + r = DLG2(3, 3); + a = 4; + } + if (!r) + r = DLG2(a, 4); + + if (!r) { + for (a = 0; a < 6; a++) + createItemOnCurrentBlock(55); + createItemOnCurrentBlock(62); + setScriptFlags(0x10000); + TXT(6); + npcJoinDialogue(7, 7, 29, 30); + } else { + TXT(5); + } + r = 1; + } + + if (!checkScriptFlags(0x80000)) { + for (a = 0; a < 6; a++) { + if (testCharacter(a, 1) && _characters[a].portrait == -9) + break; + } + if (a != 6) { + TXT(25); + TXT(26); + setScriptFlags(0x80000); + r = 1; + } + } + + if (!checkScriptFlags(0x100000)) { + if (deletePartyItems(6, -1)) { + _npcSequenceSub = 0; + drawNpcScene(npcIndex); + TXT(28); + createItemOnCurrentBlock(32); + setScriptFlags(0x100000); + r = 1; + } + } + + if (!r) + _txt->printDialogueText(_npcStrings[0][0], true); + + break; + + case 2: + if (checkScriptFlags(0x10000)) { + if (checkScriptFlags(0x20000)) { + TXT(11); + } else { + r = DLG2A3(!countResurrectionCandidates(), 9, 5, 6); + if (r < 2) { + if (r == 0) + healParty(); + else + resurrectionSelectDialogue(); + setScriptFlags(0x20000); + } + } + } else { + TXT(24); + } + break; + + case 3: + if (!DLG2(18, 7)) { + setScriptFlags(0x8400000); + for (a = 0; a < 30; a++) { + if (_monsters[a].mode == 8) + _monsters[a].mode = 5; + } + } else if (deletePartyItems(49, -1)) { + TXT(20); + setScriptFlags(0x400000); + } else { + TXT(19); + } + break; + + case 4: + r = DLG3(14, 8); + if (r == 0) + setScriptFlags(0x200000); + else if (r == 1) + TXT(15); + setScriptFlags(0x800000); + break; + + case 5: + if (!DLG2(16, 9)) { + TXT(17); + for (a = 0; a < 6; a++) { + for (r = 0; r < 2; r++) { + itm = _characters[a].inventory[r]; + if (itm && (_items[itm].type < 51 || _items[itm].type > 56)) { + _characters[a].inventory[r] = 0; + setItemPosition((Item *)&_levelBlockProperties[_currentBlock].drawObjects, _currentBlock, itm, _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]); + } + } + } + } + setScriptFlags(0x2000000); + break; + + case 6: + TXT(21); + setScriptFlags(0x1000000); + break; + + case 7: + r = DLG3(22, 10); + if (r < 2) { + if (r == 0) + npcJoinDialogue(8, 27, 44, 45); + else + TXT(31); + setScriptFlags(0x4000000); + } + break; + + default: + break; + } +} + +#undef TXT +#undef DLG2 +#undef DLG3 +#undef DLG2A3 + +void EoBEngine::updateUsedCharacterHandItem(int charIndex, int slot) { + EoBItem *itm = &_items[_characters[charIndex].inventory[slot]]; + if (itm->type == 48) { + int charges = itm->flags & 0x3F; + if (--charges) + --itm->flags; + else + deleteInventoryItem(charIndex, slot); + } else if (itm->type == 34 || itm->type == 35) { + deleteInventoryItem(charIndex, slot); + } +} + +void EoBEngine::replaceMonster(int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem) { + if (_levelBlockProperties[block].flags & 7) + return; + + for (int i = 0; i < 30; i++) { + if (_monsters[i].hitPointsCur <= 0) { + initMonster(i, unit, block, pos, dir, type, shpIndex, mode, h2, randItem, fixedItem); + break; + } + } +} + +bool EoBEngine::killMonsterExtra(EoBMonsterInPlay *m) { + if (m->type == 21) { + _playFinale = true; + _runFlag = false; + } + return true; +} + +void EoBEngine::updateScriptTimersExtra() { + int cnt = 0; + for (int i = 1; i < 30; i++) { + if (_monsters[i].hitPointsCur <= 0) + cnt++; + } + + if (!cnt) { + for (int i = 1; i < 30; i++) { + if (getBlockDistance(_monsters[i].block, _currentBlock) > 3) { + killMonster(&_monsters[i], true); + break; + } + } + } +} + +void EoBEngine::loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2) { + _screen->loadShapeSetBitmap("DOOR", 5, 3); + _screen->_curPage = 2; + + if (doorType1 != 0xFF) { + for (int i = 0; i < 3; i++) { + const uint8 *enc = &_doorShapeEncodeDefs[(doorType1 * 3 + i) << 2]; + _doorShapes[shapeId1 + i] = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); + enc = &_doorSwitchShapeEncodeDefs[(doorType1 * 3 + i) << 2]; + _doorSwitches[shapeId1 + i].shp = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); + _doorSwitches[shapeId1 + i].x = _doorSwitchCoords[doorType1 * 6 + i * 2]; + _doorSwitches[shapeId1 + i].y = _doorSwitchCoords[doorType1 * 6 + i * 2 + 1]; + } + } + + if (doorType2 != 0xFF) { + for (int i = 0; i < 3; i++) { + const uint8 *enc = &_doorShapeEncodeDefs[(doorType2 * 3 + i) << 2]; + _doorShapes[shapeId2 + i] = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); + enc = &_doorSwitchShapeEncodeDefs[(doorType2 * 3 + i) << 2]; + _doorSwitches[shapeId2 + i].shp = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); + _doorSwitches[shapeId2 + i].x = _doorSwitchCoords[doorType2 * 6 + i * 2]; + _doorSwitches[shapeId2 + i].y = _doorSwitchCoords[doorType2 * 6 + i * 2 + 1]; + } + } + + _screen->_curPage = 0; +} + +void EoBEngine::drawDoorIntern(int type, int index, int x, int y, int w, int wall, int mDim, int16 y1, int16 y2) { + int shapeIndex = type + 2 - mDim; + uint8 *shp = _doorShapes[shapeIndex]; + if (!shp) + return; + + int d1 = 0; + int d2 = 0; + int v = 0; + const ScreenDim *td = _screen->getScreenDim(5); + + switch (_currentLevel) { + case 4: + case 5: + case 6: + y = _dscDoorY6[mDim] - shp[1]; + d1 = _dscDoorCoordsExt[index << 1] >> 3; + d2 = _dscDoorCoordsExt[(index << 1) + 1] >> 3; + if (_shpDmX1 > d1) + d1 = _shpDmX1; + if (_shpDmX2 < d2) + d2 = _shpDmX2; + _screen->modifyScreenDim(5, d1, td->sy, d2 - d1, td->h); + v = ((wall < 30) ? (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult3[mDim] : _dscDoorScaleMult4[mDim]) * -1; + v -= (shp[2] << 3); + drawBlockObject(0, 2, shp, x + v, y, 5); + v += (shp[2] << 3); + drawBlockObject(1, 2, shp, x - v, y, 5); + if (_wllShapeMap[wall] == -1) + drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w - v, _doorSwitches[shapeIndex].y, 5); + break; + + case 7: + case 8: + case 9: + y = _dscDoorY3[mDim] - _doorShapes[shapeIndex + 3][1]; + d1 = x - (_doorShapes[shapeIndex + 3][2] << 2); + x -= (shp[2] << 2); + drawBlockObject(0, 2, _doorShapes[shapeIndex + 3], d1, y, 5); + setDoorShapeDim(index, y1, y2, 5); + y = _dscDoorY3[mDim] - ((wall < 30) ? (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult1[mDim] : _dscDoorScaleMult2[mDim]); + drawBlockObject(0, 2, shp, x, y, 5); + if (_wllShapeMap[wall] == -1) + drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); + break; + + case 10: + case 11: + v = ((wall < 30) ? (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult5[mDim] : _dscDoorScaleMult6[mDim]) * -1; + x -= (shp[2] << 2); + y = _dscDoorY4[mDim] + v; + drawBlockObject(0, 2, shp, x, y + v, 5); + v = (v >> 3) + (v >> 2); + y = _dscDoorY5[mDim]; + drawBlockObject(0, 2, _doorShapes[shapeIndex + 3], x, y - v, 5); + if (_wllShapeMap[wall] == -1) + drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); + break; + + default: + y = (_currentLevel == 12 ? _dscDoorY6[mDim] : _dscDoorY1[mDim]) - shp[1]; + x -= (shp[2] << 2); + y -= (wall >= 30 ? _dscDoorScaleMult2[mDim] : (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult1[mDim]); + drawBlockObject(0, 2, shp, x, y, 5); + + if (_wllShapeMap[wall] == -1) + drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); + break; + } +} + +void EoBEngine::turnUndeadAuto() { + if (_currentLevel != 2 && _currentLevel != 7) + return; + + int oc = _openBookChar; + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 0x0D)) + continue; + + EoBCharacter *c = &_characters[i]; + + if (_itemTypes[_items[c->inventory[0]].type].extraProperties != 6 && _itemTypes[_items[c->inventory[1]].type].extraProperties != 6) + continue; + + int l = getCharacterLevelIndex(2, c->cClass); + if (l > -1) { + if (c->level[l] > _openBookCasterLevel) { + _openBookCasterLevel = c->level[l]; + _openBookChar = i; + } + } else { + l = getCharacterLevelIndex(4, c->cClass); + if (l > -1) { + if ((c->level[l] - 2) > _openBookCasterLevel) { + _openBookCasterLevel = (c->level[l] - 2); + _openBookChar = i; + } + } + } + } + + if (_openBookCasterLevel) + spellCallback_start_turnUndead(); + + _openBookChar = oc; + _openBookCasterLevel = 0; +} + +void EoBEngine::turnUndeadAutoHit() { + _txt->printMessage(_turnUndeadString[0], -1, _characters[_openBookChar].name); + sparkEffectOffensive(); +} + +bool EoBEngine::checkPartyStatusExtra() { + _screen->copyPage(0, 10); + int cd = _screen->curDimIndex(); + gui_drawBox(0, 121, 320, 80, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + _txt->setupField(9, false); + _txt->printMessage(_menuStringsDefeat[0]); + while (!shouldQuit()) { + removeInputTop(); + if (checkInput(0, false, 0) & 0xFF) + break; + } + _screen->copyPage(10, 0); + _eventList.clear(); + _screen->setScreenDim(cd); + _txt->removePageBreakFlag(); + return true; +} + +int EoBEngine::resurrectionSelectDialogue() { + gui_drawDialogueBox(); + _txt->printDialogueText(_npcStrings[0][1]); + + int r = _rrId[runDialogue(-1, 9, _rrNames[0], _rrNames[1], _rrNames[2], _rrNames[3], _rrNames[4], _rrNames[5], _rrNames[6], _rrNames[7], _rrNames[8]) - 1]; + + if (r < 0) { + r = -r; + deletePartyItems(33, r); + _npcSequenceSub = r - 1; + drawNpcScene(2); + npcJoinDialogue(_npcSequenceSub, 32 + (_npcSequenceSub << 1), -1, 33 + (_npcSequenceSub << 1)); + } else { + _characters[r].hitPointsCur = _characters[r].hitPointsMax; + } + + return 1; +} + +void EoBEngine::healParty() { + int cnt = rollDice(1, 3, 2); + for (int i = 0; i < 6 && cnt; i++) { + if (testCharacter(i, 3)) + continue; + + _characters[i].flags &= ~4; + neutralizePoison(i); + + if (_characters[i].hitPointsCur >= _characters[i].hitPointsMax) + continue; + + cnt--; + _characters[i].hitPointsCur += rollDice(1, 8, 9); + if (_characters[i].hitPointsCur > _characters[i].hitPointsMax) + _characters[i].hitPointsCur = _characters[i].hitPointsMax; + } +} + +const KyraRpgGUISettings *EoBEngine::guiSettings() { + return (_configRenderMode == Common::kRenderCGA || _configRenderMode == Common::kRenderEGA) ? &_guiSettingsEGA : &_guiSettingsVGA; +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/eob.h b/engines/kyra/engine/eob.h new file mode 100644 index 0000000000..0eb8fd3a64 --- /dev/null +++ b/engines/kyra/engine/eob.h @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_EOB + +#ifndef KYRA_EOB1_H +#define KYRA_EOB1_H + +#include "kyra/engine/eobcommon.h" + +namespace Kyra { + +class EoBEngine : public EoBCoreEngine { +friend class GUI_EoB; +friend class EoBIntroPlayer; +public: + EoBEngine(OSystem *system, const GameFlags &flags); + ~EoBEngine(); + +private: + // Init / Release + Common::Error init(); + void initStaticResource(); + void initSpells(); + + // Main Menu + int mainMenu(); + int mainMenuLoop(); + int _menuChoiceInit; + + // Main loop + void startupNew(); + void startupLoad(); + + // Intro/Outro + void seq_playIntro(); + void seq_playFinale(); + void seq_xdeath(); + + const char *const *_finBonusStrings; + + // characters + void drawNpcScene(int npcIndex); + void encodeDrawNpcSeqShape(int npcIndex, int drawX, int drawY); + void runNpcDialogue(int npcIndex); + + const uint8 *_npcShpData; + const uint8 *_npcSubShpIndex1; + const uint8 *_npcSubShpIndex2; + const uint8 *_npcSubShpY; + const char *const *_npcStrings[11]; + + // items + void updateUsedCharacterHandItem(int charIndex, int slot); + + // Monsters + void replaceMonster(int unit, uint16 block, int d, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem); + bool killMonsterExtra(EoBMonsterInPlay *m); + void updateScriptTimersExtra(); + + // Level + const uint8 *loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs) { return 0; } + void loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2); + void drawDoorIntern(int type, int index, int x, int y, int w, int wall, int mDim, int16 y1, int16 y2); + + const int16 *_dscDoorCoordsExt; + const uint8 *_dscDoorScaleMult4; + const uint8 *_dscDoorScaleMult5; + const uint8 *_dscDoorScaleMult6; + const uint8 *_dscDoorY3; + const uint8 *_dscDoorY4; + const uint8 *_dscDoorY5; + const uint8 *_dscDoorY6; + const uint8 *_dscDoorY7; + + const uint8 *_doorShapeEncodeDefs; + const uint8 *_doorSwitchShapeEncodeDefs; + const uint8 *_doorSwitchCoords; + + // Fight + static const uint8 _monsterAcHitChanceTbl1[]; + static const uint8 _monsterAcHitChanceTbl2[]; + + // Magic + void turnUndeadAuto(); + void turnUndeadAutoHit(); + + const char *const *_turnUndeadString; + + // Misc + bool checkPartyStatusExtra(); + int resurrectionSelectDialogue(); + void healParty(); + + const KyraRpgGUISettings *guiSettings(); + + static const KyraRpgGUISettings _guiSettingsVGA; + static const KyraRpgGUISettings _guiSettingsEGA; + static const uint8 _egaDefaultPalette[]; +}; + +} // End of namespace Kyra + +#endif + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/eobcommon.cpp b/engines/kyra/engine/eobcommon.cpp new file mode 100644 index 0000000000..58cc394abd --- /dev/null +++ b/engines/kyra/engine/eobcommon.cpp @@ -0,0 +1,2536 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/kyra_rpg.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound_intern.h" +#include "kyra/sound/sound_adlib.h" +#include "kyra/script/script_eob.h" +#include "kyra/engine/timer.h" +#include "kyra/gui/debugger.h" + +#include "common/config-manager.h" +#include "common/translation.h" + +#include "backends/keymapper/keymapper.h" + +namespace Kyra { + +const char *const EoBCoreEngine::kKeymapName = "eob"; + +EoBCoreEngine::EoBCoreEngine(OSystem *system, const GameFlags &flags) + : KyraRpgEngine(system, flags), _numLargeItemShapes(flags.gameID == GI_EOB1 ? 14 : 11), + _numSmallItemShapes(flags.gameID == GI_EOB1 ? 23 : 26), + _numThrownItemShapes(flags.gameID == GI_EOB1 ? 12 : 9), + _numItemIconShapes(flags.gameID == GI_EOB1 ? 89 : 112), + _teleporterWallId(flags.gameID == GI_EOB1 ? 52 : 44) { + + _screen = 0; + _gui = 0; + _debugger = 0; + + _playFinale = false; + _runFlag = true; + _configMouse = _config2431 = true; + _loading = false; + + _enableHiResDithering = false; + + _envAudioTimer = 0; + _flashShapeTimer = 0; + _drawSceneTimer = 0; + + _largeItemShapes = _smallItemShapes = _thrownItemShapes = _spellShapes = _firebeamShapes = 0; + _itemIconShapes = _wallOfForceShapes = _teleporterShapes = _sparkShapes = _compassShapes = 0; + _redSplatShape = _greenSplatShape = _deadCharShape = _disabledCharGrid = 0; + _blackBoxSmallGrid = _weaponSlotGrid = _blackBoxWideGrid = _lightningColumnShape = 0; + + _monsterAcHitChanceTable1 = _monsterAcHitChanceTable2 = 0; + + _monsterDustStrings = 0; + _enemyMageSpellList = 0; + _enemyMageSfx = 0; + _beholderSpellList = 0; + _beholderSfx = 0; + + _faceShapes = 0; + _characters = 0; + _items = 0; + _itemTypes = 0; + _itemNames = 0; + _itemInHand = -1; + _numItems = _numItemNames = 0; + + _castScrollSlot = 0; + _currentSub = 0; + + _itemsOverlay = 0; + + _partyEffectFlags = 0; + _lastUsedItem = 0; + + _levelDecorationRects = 0; + _doorSwitches = 0; + _monsterProps = 0; + _monsterDecorations = 0; + _monsterFlashOverlay = _monsterStoneOverlay = 0; + _monsters = 0; + _dstMonsterIndex = 0; + _preventMonsterFlash = false; + + _teleporterPulse = 0; + + _dscShapeCoords = 0; + _dscItemPosIndex = 0; + _dscItemShpX = 0; + _dscItemScaleIndex = 0; + _dscItemTileIndex = 0; + _dscItemShapeMap = 0; + _dscDoorScaleOffs = 0; + _dscDoorScaleMult1 = 0; + _dscDoorScaleMult2 = 0; + _dscDoorScaleMult3 = 0; + _dscDoorY1 = 0; + _dscDoorXE = 0; + + _greenFadingTable = _blueFadingTable = _lightBlueFadingTable = _blackFadingTable = _greyFadingTable = 0; + + _menuDefs = 0; + + _exchangeCharacterId = -1; + _charExchangeSwap = 0; + _configHpBarGraphs = true; + _configMouseBtSwap = false; + + memset(_dialogueLastBitmap, 0, 13); + _npcSequenceSub = 0; + _moveCounter = 0; + _partyResting = false; + + _flyingObjects = 0; + + _inf = 0; + _stepCounter = 0; + _stepsUntilScriptCall = 0; + _scriptTimersMode = 3; + _currentDirection = 0; + + _openBookSpellLevel = 0; + _openBookSpellSelectedItem = 0; + _openBookSpellListOffset = 0; + _openBookChar = _openBookCharBackup = _openBookCasterLevel = 0; + _openBookType = _openBookTypeBackup = 0; + _openBookSpellList = 0; + _openBookAvailableSpells = 0; + _activeSpellCharId = 0; + _activeSpellCharacterPos = 0; + _activeSpell = 0; + _characterSpellTarget = 0; + _returnAfterSpellCallback = false; + _spells = 0; + _spellAnimBuffer = 0; + _clericSpellOffset = 0; + _restPartyElapsedTime = 0; + _allowSkip = false; + _allowImport = false; + + _wallsOfForce = 0; + + _rrCount = 0; + memset(_rrNames, 0, 10 * sizeof(const char *)); + memset(_rrId, 0, 10 * sizeof(int8)); + + _mainMenuStrings = _levelGainStrings = _monsterSpecAttStrings = _characterGuiStringsHp = 0; + _characterGuiStringsWp = _characterGuiStringsWr = _characterGuiStringsSt = 0; + _characterGuiStringsIn = _characterStatusStrings7 = _characterStatusStrings8 = 0; + _characterStatusStrings9 = _characterStatusStrings12 = _characterStatusStrings13 = 0; + _classModifierFlags = _saveThrowLevelIndex = _saveThrowModDiv = _saveThrowModExt = 0; + _wandTypes = _drawObjPosIndex = _flightObjFlipIndex = _expObjectTblIndex = 0; + _expObjectShpStart = _expObjectTlMode = _expObjectAnimTbl1 = _expObjectAnimTbl2 = _expObjectAnimTbl3 = 0; + _monsterStepTable0 = _monsterStepTable1 = _monsterStepTable2 = _monsterStepTable3 = 0; + _projectileWeaponAmmoTypes = _flightObjShpMap = _flightObjSclIndex = 0; + _monsterCloseAttPosTable1 = _monsterCloseAttPosTable2 = _monsterCloseAttChkTable1 = 0; + _monsterCloseAttChkTable2 = _monsterCloseAttDstTable1 = _monsterCloseAttDstTable2 = 0; + _monsterProximityTable = _findBlockMonstersTable = _wallOfForceDsY = _wallOfForceDsNumW = 0; + _wallOfForceDsNumH = _wallOfForceShpId = _wllFlagPreset = _teleporterShapeCoords = 0; + _monsterCloseAttUnkTable = _monsterFrmOffsTable1 = _monsterFrmOffsTable2 = 0; + _monsterDirChangeTable = _portalSeq = 0; + _wallOfForceDsX = 0; + _expObjectAnimTbl1Size = _expObjectAnimTbl2Size = _expObjectAnimTbl3Size = 0; + _wllFlagPresetSize = _scriptTimersCount = _buttonList1Size = _buttonList2Size = 0; + _buttonList3Size = _buttonList4Size = _buttonList5Size = _buttonList6Size = 0; + _buttonList7Size = _buttonList8Size = 0; + _inventorySlotsY = _mnDef = 0; + _transferStringsScummVM = 0; + _buttonDefs = 0; + _npcPreset = 0; + _chargenStatStrings = _chargenRaceSexStrings = _chargenClassStrings = 0; + _chargenAlignmentStrings = _pryDoorStrings = _warningStrings = _ripItemStrings = 0; + _cursedString = _enchantedString = _magicObjectStrings = _magicObjectString5 = 0; + _patternSuffix = _patternGrFix1 = _patternGrFix2 = _validateArmorString = 0; + _validateCursedString = _validateNoDropString = _potionStrings = _wandStrings = 0; + _itemMisuseStrings = _suffixStringsRings = _suffixStringsPotions = 0; + _suffixStringsWands = _takenStrings = _potionEffectStrings = _yesNoStrings = 0; + _npcMaxStrings = _okStrings = _npcJoinStrings = _cancelStrings = 0; + _abortStrings = _saveLoadStrings = _mnWord = _mnPrompt = _bookNumbers = 0; + _mageSpellList = _clericSpellList = _spellNames = _magicStrings1 = 0; + _magicStrings2 = _magicStrings3 = _magicStrings4 = _magicStrings6 = 0; + _magicStrings7 = _magicStrings8 = _saveNamePatterns = 0; + _spellAnimBuffer = 0; + _sparkEffectDefSteps = _sparkEffectDefSubSteps = _sparkEffectDefShift = 0; + _sparkEffectDefAdd = _sparkEffectDefX = _sparkEffectDefY = _sparkEffectOfShift = 0; + _sparkEffectOfX = _sparkEffectOfY = _magicFlightObjectProperties = 0; + _turnUndeadEffect = _burningHandsDest = _coneOfColdGfxTbl = 0; + _sparkEffectOfFlags1 = _sparkEffectOfFlags2 = 0; + _coneOfColdDest1 = _coneOfColdDest2 = _coneOfColdDest3 = _coneOfColdDest4 = 0; + _coneOfColdGfxTblSize = 0; + _menuButtonDefs = 0; + _updateCharNum = 0; + _menuStringsMain = _menuStringsSaveLoad = _menuStringsOnOff = _menuStringsSpells = 0; + _menuStringsRest = _menuStringsDrop = _menuStringsExit = _menuStringsStarve = 0; + _menuStringsScribe = _menuStringsDrop2 = _menuStringsHead = _menuStringsPoison = 0; + _menuStringsMgc = _menuStringsPrefs = _menuStringsRest2 = _menuStringsRest3 = 0; + _menuStringsRest4 = _menuStringsDefeat = _menuStringsTransfer = _menuStringsSpec = 0; + _menuStringsSpellNo = _menuYesNoStrings = _2431Strings = _katakanaLines = _katakanaSelectStrings = 0; + _errorSlotEmptyString = _errorSlotNoNameString = _menuOkString = 0; + _spellLevelsMage = _spellLevelsCleric = _numSpellsCleric = _numSpellsWisAdj = _numSpellsPal = _numSpellsMage = 0; + _mnNumWord = _numSpells = _mageSpellListSize = _spellLevelsMageSize = _spellLevelsClericSize = 0; + _inventorySlotsX = _slotValidationFlags = _encodeMonsterShpTable = 0; + _cgaMappingDefault = _cgaMappingAlt = _cgaMappingInv = _cgaLevelMappingIndex = _cgaMappingItemsL = _cgaMappingItemsS = _cgaMappingThrown = _cgaMappingIcons = _cgaMappingDeco = 0; + memset(_cgaMappingLevel, 0, sizeof(_cgaMappingLevel)); + memset(_expRequirementTables, 0, sizeof(_expRequirementTables)); + memset(_saveThrowTables, 0, sizeof(_saveThrowTables)); + memset(_doorType, 0, sizeof(_doorType)); + memset(_noDoorSwitch, 0, sizeof(_noDoorSwitch)); + memset(_scriptTimers, 0, sizeof(_scriptTimers)); + memset(_monsterBlockPosArray, 0, sizeof(_monsterBlockPosArray)); + memset(_foundMonstersArray, 0, sizeof(_foundMonstersArray)); + +#define DWM0 _dscWallMapping.push_back(0) +#define DWM(x) _dscWallMapping.push_back(&_sceneDrawVar##x) + DWM0; DWM0; DWM(Down); DWM(Right); + DWM(Down); DWM(Right); DWM(Down); DWM0; + DWM(Down); DWM(Left); DWM(Down); DWM(Left); + DWM0; DWM0; DWM(Down); DWM(Right); + DWM(Down); DWM(Right); DWM(Down); DWM0; + DWM(Down); DWM(Left); DWM(Down); DWM(Left); + DWM(Down); DWM(Right); DWM(Down); DWM0; + DWM(Down); DWM(Left); DWM0; DWM(Right); + DWM(Down); DWM0; DWM0; DWM(Left); +#undef DWM +#undef DWM0 +} + +EoBCoreEngine::~EoBCoreEngine() { + releaseItemsAndDecorationsShapes(); + releaseTempData(); + + if (_faceShapes) { + for (int i = 0; i < 44; i++) { + if (_characters) { + for (int ii = 0; ii < 6; ii++) { + if (_characters[ii].faceShape == _faceShapes[i]) + _characters[ii].faceShape = 0; + } + } + delete[] _faceShapes[i]; + _faceShapes[i] = 0; + } + delete[] _faceShapes; + } + + if (_characters) { + for (int i = 0; i < 6; i++) + delete[] _characters[i].faceShape; + } + + delete[] _characters; + delete[] _items; + delete[] _itemTypes; + if (_itemNames) { + for (int i = 0; i < 130; i++) + delete[] _itemNames[i]; + } + delete[] _itemNames; + delete[] _flyingObjects; + + delete[] _monsterFlashOverlay; + delete[] _monsterStoneOverlay; + delete[] _monsters; + + if (_monsterDecorations) { + releaseMonsterShapes(0, 36); + delete[] _monsterShapes; + delete[] _monsterDecorations; + + for (int i = 0; i < 24; i++) + delete[] _monsterPalettes[i]; + delete[] _monsterPalettes; + } + + delete[] _monsterProps; + + if (_doorSwitches) { + releaseDoorShapes(); + delete[] _doorSwitches; + } + + releaseDecorations(); + delete[] _levelDecorationRects; + _dscWallMapping.clear(); + + delete[] _greenFadingTable; + delete[] _blueFadingTable; + delete[] _lightBlueFadingTable; + delete[] _blackFadingTable; + delete[] _greyFadingTable; + + delete[] _spells; + delete[] _spellAnimBuffer; + delete[] _wallsOfForce; + delete[] _buttonDefs; + + delete _gui; + _gui = 0; + delete _screen; + _screen = 0; + + delete[] _menuDefs; + _menuDefs = 0; + + delete _inf; + _inf = 0; + delete _timer; + _timer = 0; + delete _debugger; + _debugger = 0; + delete _txt; + _txt = 0; +} + +void EoBCoreEngine::initKeymap() { +#ifdef ENABLE_KEYMAPPER + Common::Keymapper *const mapper = _eventMan->getKeymapper(); + + // Do not try to recreate same keymap over again + if (mapper->getKeymap(kKeymapName) != 0) + return; + + Common::Keymap *const engineKeyMap = new Common::Keymap(kKeymapName); + + const Common::KeyActionEntry keyActionEntries[] = { + { Common::KeyState(Common::KEYCODE_UP), "MVF", _("Move Forward") }, + { Common::KeyState(Common::KEYCODE_DOWN), "MVB", _("Move Back") }, + { Common::KeyState(Common::KEYCODE_LEFT), "MVL", _("Move Left") }, + { Common::KeyState(Common::KEYCODE_RIGHT), "MVR", _("Move Right") }, + { Common::KeyState(Common::KEYCODE_HOME), "TL", _("Turn Left") }, + { Common::KeyState(Common::KEYCODE_PAGEUP), "TR", _("Turn Right") }, + { Common::KeyState(Common::KEYCODE_i), "INV", _("Open/Close Inventory") }, + { Common::KeyState(Common::KEYCODE_p), "SCE", _("Switch Inventory/Character screen") }, + { Common::KeyState(Common::KEYCODE_c), "CMP", _("Camp") }, + { Common::KeyState(Common::KEYCODE_SPACE), "CSP", _("Cast Spell") }, + // TODO: Spell cursor, but this needs more thought, since different + // game versions use different keycodes. + { Common::KeyState(Common::KEYCODE_1), "SL1", _("Spell Level 1") }, + { Common::KeyState(Common::KEYCODE_2), "SL2", _("Spell Level 2") }, + { Common::KeyState(Common::KEYCODE_3), "SL3", _("Spell Level 3") }, + { Common::KeyState(Common::KEYCODE_4), "SL4", _("Spell Level 4") }, + { Common::KeyState(Common::KEYCODE_5), "SL5", _("Spell Level 5") } + }; + + for (uint i = 0; i < ARRAYSIZE(keyActionEntries); ++i) { + Common::Action *const act = new Common::Action(engineKeyMap, keyActionEntries[i].id, keyActionEntries[i].description); + act->addKeyEvent(keyActionEntries[i].ks); + } + + if (_flags.gameID == GI_EOB2) { + Common::Action *const act = new Common::Action(engineKeyMap, "SL6", _("Spell Level 6")); + act->addKeyEvent(Common::KeyState(Common::KEYCODE_6)); + } + + mapper->addGameKeymap(engineKeyMap); +#endif +} + +Common::Error EoBCoreEngine::init() { + // In EOB the timer proc is directly invoked via interrupt 0x1C, 18.2 times per second. + // This makes a tick length of 54.94. + _tickLength = 55; + + if (ConfMan.hasKey("render_mode")) + _configRenderMode = Common::parseRenderMode(ConfMan.get("render_mode")); + + _enableHiResDithering = (_configRenderMode == Common::kRenderEGA && _flags.useHiRes); + + _screen = new Screen_EoB(this, _system); + assert(_screen); + _screen->setResolution(); + + _res = new Resource(this); + assert(_res); + _res->reset(); + + _staticres = new StaticResource(this); + assert(_staticres); + if (!_staticres->init()) + error("_staticres->init() failed"); + + // SoundTowns_Darkmoon requires initialized _staticres + if (_flags.platform == Common::kPlatformDOS) { + //MidiDriverType midiDriver = MidiDriver::detectDevice(MDT_PCSPK | MDT_ADLIB); + _sound = new SoundAdLibPC(this, _mixer); + } else if (_flags.platform == Common::kPlatformFMTowns) { + _sound = new SoundTowns_Darkmoon(this, _mixer); + } else if (_flags.platform == Common::kPlatformPC98) { + + } + + assert(_sound); + _sound->init(); + + // Setup volume settings (and read in all ConfigManager settings) + syncSoundSettings(); + + if (!_screen->init()) + error("screen()->init() failed"); + + if (ConfMan.hasKey("save_slot")) { + _gameToLoad = ConfMan.getInt("save_slot"); + if (!saveFileLoadable(_gameToLoad)) + _gameToLoad = -1; + } + + setupKeyMap(); + + _gui = new GUI_EoB(this); + assert(_gui); + _txt = new TextDisplayer_rpg(this, _screen); + assert(_txt); + _inf = new EoBInfProcessor(this, _screen); + assert(_inf); + _debugger = new Debugger_EoB(this); + assert(_debugger); + + _screen->loadFont(Screen::FID_6_FNT, "FONT6.FNT"); + _screen->loadFont(Screen::FID_8_FNT, "FONT8.FNT"); + + Common::Error err = KyraRpgEngine::init(); + if (err.getCode() != Common::kNoError) + return err; + + initButtonData(); + initMenus(); + initStaticResource(); + initSpells(); + + _timer = new TimerManager(this, _system); + assert(_timer); + setupTimers(); + + _wllVmpMap[1] = 1; + _wllVmpMap[2] = 2; + memset(&_wllVmpMap[3], 3, 20); + _wllVmpMap[23] = 4; + _wllVmpMap[24] = 5; + + memcpy(_wllWallFlags, _wllFlagPreset, _wllFlagPresetSize); + + memset(&_specialWallTypes[3], 1, 5); + memset(&_specialWallTypes[13], 1, 5); + _specialWallTypes[8] = _specialWallTypes[18] = 6; + + memset(&_wllShapeMap[3], -1, 5); + memset(&_wllShapeMap[13], -1, 5); + + _wllVcnOffset = (_flags.platform == Common::kPlatformFMTowns) ? 0 : 16; + int bpp = (_flags.platform == Common::kPlatformFMTowns) ? 2 : 1; + + _greenFadingTable = new uint8[256 * bpp]; + _blueFadingTable = new uint8[256 * bpp]; + _lightBlueFadingTable = new uint8[256 * bpp]; + _blackFadingTable = new uint8[256 * bpp]; + _greyFadingTable = new uint8[256 * bpp]; + + _monsters = new EoBMonsterInPlay[30]; + memset(_monsters, 0, 30 * sizeof(EoBMonsterInPlay)); + + _characters = new EoBCharacter[6]; + memset(_characters, 0, sizeof(EoBCharacter) * 6); + + _items = new EoBItem[600]; + memset(_items, 0, sizeof(EoBItem) * 600); + + _itemNames = new char*[130]; + for (int i = 0; i < 130; i++) { + _itemNames[i] = new char[35]; + memset(_itemNames[i], 0, 35); + } + + _flyingObjects = new EoBFlyingObject[_numFlyingObjects]; + _flyingObjectsPtr = _flyingObjects; + memset(_flyingObjects, 0, _numFlyingObjects * sizeof(EoBFlyingObject)); + + int bufferSize = _flags.useHiColorMode ? 8192 : 4096; + _spellAnimBuffer = new uint8[bufferSize]; + memset(_spellAnimBuffer, 0, bufferSize); + + _wallsOfForce = new WallOfForce[5]; + memset(_wallsOfForce, 0, 5 * sizeof(WallOfForce)); + + memset(_doorType, 0, sizeof(_doorType)); + memset(_noDoorSwitch, 0, sizeof(_noDoorSwitch)); + + _monsterShapes = new uint8*[36]; + memset(_monsterShapes, 0, 36 * sizeof(uint8 *)); + _monsterDecorations = new SpriteDecoration[36]; + memset(_monsterDecorations, 0, 36 * sizeof(SpriteDecoration)); + _monsterPalettes = new uint8*[24]; + for (int i = 0; i < 24; i++) + _monsterPalettes[i] = new uint8[16]; + + _doorSwitches = new SpriteDecoration[6]; + memset(_doorSwitches, 0, 6 * sizeof(SpriteDecoration)); + + _monsterFlashOverlay = new uint8[16]; + _monsterStoneOverlay = new uint8[16]; + memset(_monsterFlashOverlay, (_configRenderMode == Common::kRenderCGA) ? 0xFF : 0x0F, 16 * sizeof(uint8)); + memset(_monsterStoneOverlay, 0x0D, 16 * sizeof(uint8)); + _monsterFlashOverlay[0] = _monsterStoneOverlay[0] = 0; + + // Prevent autosave on game startup + _lastAutosave = _system->getMillis(); + +#ifdef ENABLE_KEYMAPPER + _eventMan->getKeymapper()->pushKeymap(kKeymapName, true); +#endif + + return Common::kNoError; +} + +Common::Error EoBCoreEngine::go() { + _debugger->initialize(); + _txt->removePageBreakFlag(); + _screen->setFont(Screen::FID_8_FNT); + loadItemsAndDecorationsShapes(); + _screen->setMouseCursor(0, 0, _itemIconShapes[0]); + + // Import original save game files (especially the "Quick Start Party") + if (ConfMan.getBool("importOrigSaves")) { + importOriginalSaveFile(-1); + ConfMan.setBool("importOrigSaves", false); + ConfMan.flushToDisk(); + } + + loadItemDefs(); + int action = 0; + + for (bool repeatLoop = true; repeatLoop; repeatLoop ^= true) { + action = 0; + + if (_gameToLoad != -1) { + _sound->selectAudioResourceSet(kMusicIngame); + if (loadGameState(_gameToLoad).getCode() != Common::kNoError) + error("Couldn't load game slot %d on startup", _gameToLoad); + startupLoad(); + _gameToLoad = -1; + } else { + _screen->showMouse(); + action = mainMenu(); + } + + _sound->selectAudioResourceSet(kMusicIngame); + + if (action == -1) { + // load game + repeatLoop = _gui->runLoadMenu(72, 14); + if (repeatLoop && !shouldQuit()) + startupLoad(); + } else if (action == -2) { + // new game + repeatLoop = startCharacterGeneration(); + if (repeatLoop && !shouldQuit()) + startupNew(); + } else if (action == -3) { + // transfer party + repeatLoop = startPartyTransfer(); + if (repeatLoop && !shouldQuit()) + startupNew(); + } + } + + if (!shouldQuit() && action >= -3) { + runLoop(); + + if (_playFinale) { + // make final save for party transfer + saveGameStateIntern(-1, 0, 0); + _sound->selectAudioResourceSet(kMusicFinale); + seq_playFinale(); + } + } + + return Common::kNoError; +} + +void EoBCoreEngine::registerDefaultSettings() { + KyraEngine_v1::registerDefaultSettings(); + ConfMan.registerDefault("hpbargraphs", true); + ConfMan.registerDefault("mousebtswap", false); + ConfMan.registerDefault("importOrigSaves", true); +} + +void EoBCoreEngine::readSettings() { + _configHpBarGraphs = ConfMan.getBool("hpbargraphs"); + _configMouseBtSwap = ConfMan.getBool("mousebtswap"); + _configSounds = ConfMan.getBool("sfx_mute") ? 0 : 1; + _configMusic = _configSounds ? 1 : 0; + + if (_sound) + _sound->enableSFX(_configSounds); +} + +void EoBCoreEngine::writeSettings() { + ConfMan.setBool("hpbargraphs", _configHpBarGraphs); + ConfMan.setBool("mousebtswap", _configMouseBtSwap); + ConfMan.setBool("sfx_mute", _configSounds == 0); + + if (_sound) { + if (!_configSounds) + _sound->haltTrack(); + _sound->enableMusic(_configSounds ? 1 : 0); + _sound->enableSFX(_configSounds); + } + + ConfMan.flushToDisk(); +} + +void EoBCoreEngine::startupNew() { + gui_setPlayFieldButtons(); + _screen->_curPage = 0; + gui_drawPlayField(false); + _screen->_curPage = 0; + gui_drawAllCharPortraitsWithStats(); + drawScene(1); + _updateFlags = 0; + _updateCharNum = 0; +} + +void EoBCoreEngine::runLoop() { + _envAudioTimer = _system->getMillis() + (rollDice(1, 10, 3) * 18 * _tickLength); + _flashShapeTimer = 0; + _drawSceneTimer = _system->getMillis(); + + _screen->setFont(Screen::FID_6_FNT); + _screen->setScreenDim(7); + + _runFlag = true; + + while (!shouldQuit() && _runFlag) { + checkPartyStatus(true); + checkInput(_activeButtons, true, 0); + removeInputTop(); + + if (!_runFlag) + break; + + _timer->update(); + updateScriptTimers(); + updateWallOfForceTimers(); + + if (_sceneUpdateRequired) + drawScene(1); + + if (_envAudioTimer >= _system->getMillis() || (_flags.gameID == GI_EOB1 && (_currentLevel == 0 || _currentLevel > 3))) + continue; + + _envAudioTimer = _system->getMillis() + (rollDice(1, 10, 3) * 18 * _tickLength); + snd_processEnvironmentalSoundEffect(_flags.gameID == GI_EOB1 ? 30 : (rollDice(1, 2, -1) ? 27 : 28), _currentBlock + rollDice(1, 12, -1)); + updateEnvironmentalSfx(0); + turnUndeadAuto(); + } +} + +bool EoBCoreEngine::checkPartyStatus(bool handleDeath) { + int numChars = 0; + for (int i = 0; i < 6; i++) + numChars += testCharacter(i, 13); + + if (numChars) + return false; + + if (!handleDeath) + return true; + + gui_drawAllCharPortraitsWithStats(); + + if (checkPartyStatusExtra()) { + _screen->setFont(Screen::FID_8_FNT); + gui_updateControls(); + if (_gui->runLoadMenu(0, 0)) { + _screen->setFont(Screen::FID_6_FNT); + return true; + } + } + + quitGame(); + return false; +} + +void EoBCoreEngine::loadItemsAndDecorationsShapes() { + releaseItemsAndDecorationsShapes(); + int div = (_flags.gameID == GI_EOB1) ? 3 : 8; + int mul = (_flags.gameID == GI_EOB1) ? 64 : 24; + int size = 0; + + _largeItemShapes = new const uint8*[_numLargeItemShapes]; + if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { + for (int i = 0; i < _numLargeItemShapes; i++) + _largeItemShapes[i] = _staticres->loadRawData(kEoB2LargeItemsShapeData00 + i, size); + } else { + _screen->loadShapeSetBitmap("ITEML1", 5, 3); + for (int i = 0; i < _numLargeItemShapes; i++) + _largeItemShapes[i] = _screen->encodeShape((i / div) << 3, (i % div) * mul, 8, 24, false, _cgaMappingItemsL); + } + + _smallItemShapes = new const uint8*[_numSmallItemShapes]; + if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { + for (int i = 0; i < _numSmallItemShapes; i++) + _smallItemShapes[i] = _staticres->loadRawData(kEoB2SmallItemsShapeData00 + i, size); + } else { + _screen->loadShapeSetBitmap("ITEMS1", 5, 3); + for (int i = 0; i < _numSmallItemShapes; i++) + _smallItemShapes[i] = _screen->encodeShape((i / div) << 2, (i % div) * mul, 4, 24, false, _cgaMappingItemsS); + } + + _thrownItemShapes = new const uint8*[_numThrownItemShapes]; + _spellShapes = new const uint8*[4]; + _firebeamShapes = new const uint8*[3]; + if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { + for (int i = 0; i < _numThrownItemShapes; i++) + _thrownItemShapes[i] = _staticres->loadRawData(kEoB2ThrownShapeData00 + i, size); + for (int i = 0; i < 4; i++) + _spellShapes[i] = _staticres->loadRawData(kEoB2SpellShapeData00 + i, size); + for (int i = 0; i < 3; i++) + _firebeamShapes[i] = _staticres->loadRawData(kEoB2FirebeamShapeData00 + i, size); + _redSplatShape = _staticres->loadRawData(kEoB2RedSplatShapeData, size); + _greenSplatShape = _staticres->loadRawData(kEoB2GreenSplatShapeData, size); + } else { + _screen->loadShapeSetBitmap("THROWN", 5, 3); + for (int i = 0; i < _numThrownItemShapes; i++) + _thrownItemShapes[i] = _screen->encodeShape((i / div) << 2, (i % div) * mul, 4, 24, false, _cgaMappingThrown); + for (int i = 0; i < 4; i++) + _spellShapes[i] = _screen->encodeShape(8, i << 5, 6, 32, false, _cgaMappingThrown); + + _firebeamShapes[0] = _screen->encodeShape(16, 0, 4, 24, false, _cgaMappingThrown); + _firebeamShapes[1] = _screen->encodeShape(16, 24, 4, 24, false, _cgaMappingThrown); + _firebeamShapes[2] = _screen->encodeShape(16, 48, 3, 24, false, _cgaMappingThrown); + _redSplatShape = _screen->encodeShape(16, _flags.gameID == GI_EOB1 ? 144 : 72, 5, 24, false, _cgaMappingThrown); + _greenSplatShape = _screen->encodeShape(16, _flags.gameID == GI_EOB1 ? 168 : 96, 5, 16, false, _cgaMappingThrown); + } + + _itemIconShapes = new const uint8*[_numItemIconShapes]; + if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { + for (int i = 0; i < _numItemIconShapes; i++) + _itemIconShapes[i] = _staticres->loadRawData(kEoB2ItemIconShapeData00 + i, size); + } else { + _screen->loadShapeSetBitmap("ITEMICN", 5, 3); + for (int i = 0; i < _numItemIconShapes; i++) + _itemIconShapes[i] = _screen->encodeShape((i % 0x14) << 1, (i / 0x14) << 4, 2, 0x10, false, _cgaMappingIcons); + } + + _teleporterShapes = new const uint8*[6]; + _sparkShapes = new const uint8*[3]; + _compassShapes = new const uint8*[12]; + if (_flags.gameID == GI_EOB2) + _wallOfForceShapes = new const uint8*[6]; + + if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { + _lightningColumnShape = _staticres->loadRawData(kEoB2LightningColumnShapeData, size); + for (int i = 0; i < 6; i++) + _wallOfForceShapes[i] = _staticres->loadRawData(kEoB2WallOfForceShapeData00 + i, size); + for (int i = 0; i < 6; i++) + _teleporterShapes[i] = _staticres->loadRawData(kEoB2TeleporterShapeData00 + i, size); + for (int i = 0; i < 3; i++) + _sparkShapes[i] = _staticres->loadRawData(kEoB2SparkShapeData00 + i, size); + for (int i = 0; i < 12; i++) + _compassShapes[i] = _staticres->loadRawData(kEoB2CompassShapeData00 + i, size); + + _deadCharShape = _staticres->loadRawData(kEoB2DeadCharShapeData, size); + _disabledCharGrid = _staticres->loadRawData(kEoB2DisabledCharGridShapeData, size); + _blackBoxSmallGrid = _staticres->loadRawData(kEoB2SmallGridShapeData, size); + _weaponSlotGrid = _staticres->loadRawData(kEoB2WeaponSlotGridShapeData, size); + _blackBoxWideGrid = _staticres->loadRawData(kEoB2WideGridShapeData, size); + + } else { + _screen->loadShapeSetBitmap("DECORATE", 5, 3); + if (_flags.gameID == GI_EOB2) { + _lightningColumnShape = _screen->encodeShape(18, 88, 4, 64); + for (int i = 0; i < 6; i++) + _wallOfForceShapes[i] = _screen->encodeShape(_wallOfForceShapeDefs[(i << 2)], _wallOfForceShapeDefs[(i << 2) + 1], _wallOfForceShapeDefs[(i << 2) + 2], _wallOfForceShapeDefs[(i << 2) + 3]); + } + + for (int i = 0; i < 6; i++) + _teleporterShapes[i] = _screen->encodeShape(_teleporterShapeDefs[(i << 2)], _teleporterShapeDefs[(i << 2) + 1], _teleporterShapeDefs[(i << 2) + 2], _teleporterShapeDefs[(i << 2) + 3], false, _cgaMappingDefault); + + _sparkShapes[0] = _screen->encodeShape(29, 0, 2, 16, false, _cgaMappingDeco); + _sparkShapes[1] = _screen->encodeShape(31, 0, 2, 16, false, _cgaMappingDeco); + _sparkShapes[2] = _screen->encodeShape(33, 0, 2, 16, false, _cgaMappingDeco); + _deadCharShape = _screen->encodeShape(0, 88, 4, 32, false, _cgaMappingDeco); + _disabledCharGrid = _screen->encodeShape(4, 88, 4, 32, false, _cgaMappingDeco); + _blackBoxSmallGrid = _screen->encodeShape(9, 88, 2, 8, false, _cgaMappingDeco); + _weaponSlotGrid = _screen->encodeShape(8, 88, 4, 16, false, _cgaMappingDeco); + _blackBoxWideGrid = _screen->encodeShape(8, 104, 4, 8, false, _cgaMappingDeco); + + static const uint8 dHeight[] = { 17, 10, 10 }; + static const uint8 dY[] = { 120, 137, 147 }; + + for (int y = 0; y < 3; y++) { + for (int x = 0; x < 4; x++) + _compassShapes[(y << 2) + x] = _screen->encodeShape(x * 3, dY[y], 3, dHeight[y], false, _cgaMappingDeco); + } + } +} + +void EoBCoreEngine::releaseItemsAndDecorationsShapes() { + if (_flags.platform != Common::kPlatformFMTowns || _flags.gameID != GI_EOB2) { + if (_largeItemShapes) { + for (int i = 0; i < _numLargeItemShapes; i++) { + if (_largeItemShapes[i]) + delete[] _largeItemShapes[i]; + } + } + + if (_smallItemShapes) { + for (int i = 0; i < _numSmallItemShapes; i++) { + if (_smallItemShapes[i]) + delete[] _smallItemShapes[i]; + } + } + + if (_thrownItemShapes) { + for (int i = 0; i < _numThrownItemShapes; i++) { + if (_thrownItemShapes[i]) + delete[] _thrownItemShapes[i]; + } + } + + if (_spellShapes) { + for (int i = 0; i < 4; i++) { + if (_spellShapes[i]) + delete[] _spellShapes[i]; + } + } + + if (_itemIconShapes) { + for (int i = 0; i < _numItemIconShapes; i++) { + if (_itemIconShapes[i]) + delete[] _itemIconShapes[i]; + } + } + + if (_sparkShapes) { + for (int i = 0; i < 3; i++) { + if (_sparkShapes[i]) + delete[] _sparkShapes[i]; + } + } + + if (_wallOfForceShapes) { + for (int i = 0; i < 6; i++) { + if (_wallOfForceShapes[i]) + delete[] _wallOfForceShapes[i]; + } + } + + if (_teleporterShapes) { + for (int i = 0; i < 6; i++) { + if (_teleporterShapes[i]) + delete[] _teleporterShapes[i]; + } + } + + if (_compassShapes) { + for (int i = 0; i < 12; i++) { + if (_compassShapes[i]) + delete[] _compassShapes[i]; + } + } + + if (_firebeamShapes) { + for (int i = 0; i < 3; i++) { + if (_firebeamShapes[i]) + delete[] _firebeamShapes[i]; + } + } + + delete[] _redSplatShape; + delete[] _greenSplatShape; + delete[] _deadCharShape; + delete[] _disabledCharGrid; + delete[] _blackBoxSmallGrid; + delete[] _weaponSlotGrid; + delete[] _blackBoxWideGrid; + delete[] _lightningColumnShape; + } + + delete[] _largeItemShapes; + delete[] _smallItemShapes; + delete[] _thrownItemShapes; + delete[] _spellShapes; + delete[] _itemIconShapes; + delete[] _sparkShapes; + delete[] _wallOfForceShapes; + delete[] _teleporterShapes; + delete[] _compassShapes; + delete[] _firebeamShapes; +} + +void EoBCoreEngine::setHandItem(Item itemIndex) { + if (itemIndex == -1) { + if (_flags.platform == Common::kPlatformFMTowns) + _screen->setMouseCursor(8, 8, _itemIconShapes[37], 0); + return; + } + + if (_screen->curDimIndex() == 7 && itemIndex) { + printFullItemName(itemIndex); + _txt->printMessage(_takenStrings[0]); + } + + _itemInHand = itemIndex; + int icon = _items[_itemInHand].icon; + const uint8 *shp = _itemIconShapes[icon]; + const uint8 *ovl = 0; + + if (icon && (_items[_itemInHand].flags & 0x80) && (_partyEffectFlags & 2)) + ovl = _flags.gameID == GI_EOB1 ? ((_configRenderMode == Common::kRenderCGA) ? _itemsOverlayCGA : &_itemsOverlay[icon << 4]) : _screen->generateShapeOverlay(shp, _lightBlueFadingTable); + + int mouseOffs = itemIndex ? 8 : 0; + _screen->setMouseCursor(mouseOffs, mouseOffs, shp, ovl); + + if (_flags.useHiColorMode) { + _screen->setFadeTable(_greyFadingTable); + _screen->setShapeFadingLevel(0); + } +} + +int EoBCoreEngine::getDexterityArmorClassModifier(int dexterity) { + static const int8 mod[] = { 5, 5, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -4, -4, -5, -5, -5, -6, -6 }; + return mod[dexterity]; +} + +int EoBCoreEngine::generateCharacterHitpointsByLevel(int charIndex, int levelIndex) { + EoBCharacter *c = &_characters[charIndex]; + int m = getClassAndConstHitpointsModifier(c->cClass, c->constitutionCur); + + int h = 0; + + for (int i = 0; i < 3; i++) { + if (!(levelIndex & (1 << i))) + continue; + + int d = getCharacterClassType(c->cClass, i); + + if (c->level[i] <= _hpIncrPerLevel[6 + i]) + h += rollDice(1, (d >= 0) ? _hpIncrPerLevel[d] : 0); + else + h += _hpIncrPerLevel[12 + i]; + + h += m; + } + + h /= _numLevelsPerClass[c->cClass]; + + if (h < 1) + h = 1; + + return h; +} + +int EoBCoreEngine::getClassAndConstHitpointsModifier(int cclass, int constitution) { + int res = _hpConstModifiers[constitution]; + // This also applies to EOB1 despite being coded differently there + if (res <= 2 || (_classModifierFlags[cclass] & 0x31)) + return res; + + return 2; +} + +int EoBCoreEngine::getCharacterClassType(int cclass, int levelIndex) { + return _characterClassType[cclass * 3 + levelIndex]; +} + +int EoBCoreEngine::getModifiedHpLimits(int hpModifier, int constModifier, int level, bool mode) { + int s = _hpIncrPerLevel[6 + hpModifier] > level ? level : _hpIncrPerLevel[6 + hpModifier]; + int res = s; + + if (!mode) + res *= (hpModifier >= 0 ? _hpIncrPerLevel[hpModifier] : 0); + + if (level > s) { + s = level - s; + res += (s * _hpIncrPerLevel[12 + hpModifier]); + } + + if (!mode || (constModifier > 0)) + res += (level * constModifier); + + return res; +} + +Common::String EoBCoreEngine::getCharStrength(int str, int strExt) { + if (strExt) { + if (strExt == 100) + strExt = 0; + _strenghtStr = Common::String::format("%d/%02d", str, strExt); + } else { + _strenghtStr = Common::String::format("%d", str); + } + + return _strenghtStr; +} + +int EoBCoreEngine::testCharacter(int16 index, int flags) { + if (index == -1) + return 0; + + EoBCharacter *c = &_characters[index]; + int res = 1; + + if (flags & 1) + res &= (c->flags & 1); + + if (flags & 2) + res &= ((c->hitPointsCur <= -10) || (c->flags & 8)) ? 0 : 1; + + if (flags & 4) + res &= ((c->hitPointsCur <= 0) || (c->flags & 8)) ? 0 : 1; + + if (flags & 8) + res &= (c->flags & 12) ? 0 : 1; + + if (flags & 0x20) + res &= (c->flags & 4) ? 0 : 1; + + if (flags & 0x10) + res &= (c->flags & 2) ? 0 : 1; + + if (flags & 0x40) + res &= (c->food <= 0) ? 0 : 1; + + return res; +} + +int EoBCoreEngine::getNextValidCharIndex(int curCharIndex, int searchStep) { + do { + curCharIndex += searchStep; + if (curCharIndex < 0) + curCharIndex = 5; + if (curCharIndex > 5) + curCharIndex = 0; + } while (!testCharacter(curCharIndex, 1)); + + return curCharIndex; +} + +void EoBCoreEngine::recalcArmorClass(int index) { + EoBCharacter *c = &_characters[index]; + int acm = getDexterityArmorClassModifier(c->dexterityCur); + c->armorClass = 10 + acm; + + static uint8 slot[] = { 17, 0, 1, 18 }; + for (int i = 0; i < 4; i++) { + int itm = c->inventory[slot[i]]; + if (!itm) + continue; + + if (i == 2) { + if (!validateWeaponSlotItem(index, 1)) + continue; + } + + int tp = _items[itm].type; + + if (!(_itemTypes[tp].allowedClasses & _classModifierFlags[c->cClass]) || (_itemTypes[tp].extraProperties & 0x7F) || (i >= 1 && i <= 2 && tp != 27 && !(_flags.gameID == GI_EOB2 && tp == 57))) + continue; + + c->armorClass += _itemTypes[tp].armorClass; + c->armorClass -= _items[itm].value; + } + + if (!_items[c->inventory[17]].value) { + int8 m1 = 0; + int8 m2 = 0; + + if (c->inventory[25]) { + if (!(_itemTypes[_items[c->inventory[25]].type].extraProperties & 0x7F)) + m1 = _items[c->inventory[25]].value; + } + + if (c->inventory[26]) { + if (!(_itemTypes[_items[c->inventory[26]].type].extraProperties & 0x7F)) + m2 = _items[c->inventory[26]].value; + } + + c->armorClass -= MAX(m1, m2); + } + + if (c->effectsRemainder[0] > 0) { + if (c->armorClass <= (acm + 6)) + c->effectsRemainder[0] = 0; + else + c->armorClass = (acm + 6); + } + + // shield + if ((c->effectFlags & 8) && (c->armorClass > 4)) + c->armorClass = 4; + + // magical vestment + if (c->effectFlags & 0x4000) { + int8 m1 = 5; + + if (getClericPaladinLevel(index) > 5) + m1 += ((getClericPaladinLevel(index) - 5) / 3); + + if (c->armorClass > m1) + c->armorClass = m1; + } + + if (c->armorClass < -10) + c->armorClass = -10; +} + +int EoBCoreEngine::validateWeaponSlotItem(int index, int slot) { + EoBCharacter *c = &_characters[index]; + int itm1 = c->inventory[0]; + int r = itemUsableByCharacter(index, itm1); + int tp1 = _items[itm1].type; + + if (!slot) + return (!itm1 || r) ? 1 : 0; + + int itm2 = c->inventory[1]; + r = itemUsableByCharacter(index, itm2); + int tp2 = _items[itm2].type; + + if (itm1 && _itemTypes[tp1].requiredHands == 2) + return 0; + + if (!itm2) + return 1; + + int f = (_itemTypes[tp2].extraProperties & 0x7F); + if (f <= 0 || f > 3) + return r; + + if (_itemTypes[tp2].requiredHands) + return 0; + + return r; +} + +int EoBCoreEngine::getClericPaladinLevel(int index) { + if (_castScrollSlot) + return 9; + + if (index == -1) + return (_currentLevel < 7) ? 5 : 9; + + int l = getCharacterLevelIndex(2, _characters[index].cClass); + if (l > -1) + return _characters[index].level[l]; + + l = getCharacterLevelIndex(4, _characters[index].cClass); + if (l > -1) { + if (_characters[index].level[l] > 8) + return _characters[index].level[l] - 8; + } + + return 1; +} + +int EoBCoreEngine::getMageLevel(int index) { + if (_castScrollSlot) + return 9; + + if (index == -1) + return (_currentLevel < 7) ? 5 : 9; + + int l = getCharacterLevelIndex(1, _characters[index].cClass); + return (l > -1) ? _characters[index].level[l] : 1; +} + +int EoBCoreEngine::getCharacterLevelIndex(int type, int cClass) { + if (getCharacterClassType(cClass, 0) == type) + return 0; + + if (getCharacterClassType(cClass, 1) == type) + return 1; + + if (getCharacterClassType(cClass, 2) == type) + return 2; + + return -1; +} + +int EoBCoreEngine::countCharactersWithSpecificItems(int16 itemType, int16 itemValue) { + int res = 0; + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (checkInventoryForItem(i, itemType, itemValue) != -1) + res++; + } + return res; +} + +int EoBCoreEngine::checkInventoryForItem(int character, int16 itemType, int16 itemValue) { + if (character < 0) + return -1; + + for (int i = 0; i < 27; i++) { + uint16 inv = _characters[character].inventory[i]; + if (!inv) + continue; + if (_items[inv].type != itemType && itemType != -1) + continue; + if (_items[inv].value == itemValue || itemValue == -1) + return i; + } + return -1; +} + +void EoBCoreEngine::modifyCharacterHitpoints(int character, int16 points) { + if (!testCharacter(character, 3)) + return; + + EoBCharacter *c = &_characters[character]; + c->hitPointsCur += points; + if (c->hitPointsCur > c->hitPointsMax) + c->hitPointsCur = c->hitPointsMax; + + gui_drawHitpoints(character); + gui_drawCharPortraitWithStats(character); +} + +void EoBCoreEngine::neutralizePoison(int character) { + _characters[character].flags &= ~2; + _characters[character].effectFlags &= ~0x2000; + deleteCharEventTimer(character, -34); + gui_drawCharPortraitWithStats(character); +} + +void EoBCoreEngine::npcSequence(int npcIndex) { + _screen->loadShapeSetBitmap("OUTTAKE", 5, 3); + _screen->copyRegion(0, 0, 0, 0, 176, 120, 0, 6, Screen::CR_NO_P_CHECK); + + drawNpcScene(npcIndex); + + Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT"); + _screen->loadFileDataToPage(s, 5, 32000); + delete s; + + gui_drawBox(0, 121, 320, 79, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + _txt->setupField(9, true); + _txt->resetPageBreakString(); + + runNpcDialogue(npcIndex); + + _txt->removePageBreakFlag(); + gui_restorePlayField(); +} + +void EoBCoreEngine::initNpc(int npcIndex) { + EoBCharacter *c = _characters; + int i = 0; + for (; i < 6; i++) { + if (!(_characters[i].flags & 1)) { + c = &_characters[i]; + break; + } + } + + delete[] c->faceShape; + memcpy(c, &_npcPreset[npcIndex], sizeof(EoBCharacter)); + recalcArmorClass(i); + + for (i = 0; i < 25; i++) { + if (!c->inventory[i]) + continue; + c->inventory[i] = duplicateItem(c->inventory[i]); + } + + _screen->loadShapeSetBitmap(_flags.gameID == GI_EOB2 ? "OUTPORTS" : "OUTTAKE", 3, 3); + _screen->_curPage = 2; + c->faceShape = _screen->encodeShape(npcIndex << 2, _flags.gameID == GI_EOB2 ? 0 : 160, 4, 32, true, _cgaMappingDefault); + _screen->_curPage = 0; +} + +int EoBCoreEngine::npcJoinDialogue(int npcIndex, int queryJoinTextId, int confirmJoinTextId, int noJoinTextId) { + gui_drawDialogueBox(); + _txt->printDialogueText(queryJoinTextId, 0); + + int r = runDialogue(-1, 2, _yesNoStrings[0], _yesNoStrings[1]) - 1; + if (r == 0) { + if (confirmJoinTextId == -1) { + Common::String tmp = Common::String::format(_npcJoinStrings[0], _npcPreset[npcIndex].name); + _txt->printDialogueText(tmp.c_str(), true); + } else { + _txt->printDialogueText(confirmJoinTextId, _okStrings[0]); + } + + if (prepareForNewPartyMember(33, npcIndex + 1)) + initNpc(npcIndex); + + } else if (r == 1) { + _txt->printDialogueText(noJoinTextId, _okStrings[0]); + } + + return r ^ 1; +} + +int EoBCoreEngine::prepareForNewPartyMember(int16 itemType, int16 itemValue) { + int numChars = 0; + for (int i = 0; i < 6; i++) + numChars += (_characters[i].flags & 1); + + if (numChars < 6) { + deletePartyItems(itemType, itemValue); + } else { + gui_drawDialogueBox(); + _screen->set16bitShadingLevel(4); + _txt->printDialogueText(_npcMaxStrings[0]); + _screen->set16bitShadingLevel(0); + int r = runDialogue(-1, 7, _characters[0].name, _characters[1].name, _characters[2].name, _characters[3].name, + _characters[4].name, _characters[5].name, _abortStrings[0]) - 1; + + if (r == 6) + return 0; + + deletePartyItems(itemType, itemValue); + removeCharacterFromParty(r); + } + + return 1; +} + +void EoBCoreEngine::dropCharacter(int charIndex) { + if (!testCharacter(charIndex, 1)) + return; + + removeCharacterFromParty(charIndex); + + if (charIndex < 5) + exchangeCharacters(charIndex, testCharacter(5, 1) ? 5 : 4); + + gui_processCharPortraitClick(0); + gui_setPlayFieldButtons(); + setupCharacterTimers(); +} + +void EoBCoreEngine::removeCharacterFromParty(int charIndex) { + EoBCharacter *c = &_characters[charIndex]; + c->flags = 0; + + for (int i = 0; i < 27; i++) { + if (i == 16 || !c->inventory[i]) + continue; + + setItemPosition((Item *)&_levelBlockProperties[_currentBlock & 0x3FF].drawObjects, _currentBlock, c->inventory[i], _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]); + c->inventory[i] = 0; + } + + while (c->inventory[16]) + setItemPosition((Item *)&_levelBlockProperties[_currentBlock & 0x3FF].drawObjects, _currentBlock, getQueuedItem(&c->inventory[16], 0, -1), _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]); + + c->inventory[16] = 0; + + if (_updateCharNum == charIndex) + _updateCharNum = 0; + + setupCharacterTimers(); +} + +void EoBCoreEngine::exchangeCharacters(int charIndex1, int charIndex2) { + EoBCharacter temp; + memcpy(&temp, &_characters[charIndex1], sizeof(EoBCharacter)); + memcpy(&_characters[charIndex1], &_characters[charIndex2], sizeof(EoBCharacter)); + memcpy(&_characters[charIndex2], &temp, sizeof(EoBCharacter)); +} + +void EoBCoreEngine::increasePartyExperience(int16 points) { + int cnt = 0; + for (int i = 0; i < 6; i++) { + if (testCharacter(i, 3)) + cnt++; + } + + if (cnt <= 0) + return; + + points /= cnt; + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 3)) + continue; + increaseCharacterExperience(i, points); + } +} + +void EoBCoreEngine::increaseCharacterExperience(int charIndex, int32 points) { + int cl = _characters[charIndex].cClass; + points /= _numLevelsPerClass[cl]; + + for (int i = 0; i < 3; i++) { + if (getCharacterClassType(cl, i) == -1) + continue; + _characters[charIndex].experience[i] += points; + + uint32 er = getRequiredExperience(cl, i, _characters[charIndex].level[i] + 1); + if (er == 0xFFFFFFFF) + continue; + + if (_characters[charIndex].experience[i] >= er) + increaseCharacterLevel(charIndex, i); + } +} + +uint32 EoBCoreEngine::getRequiredExperience(int cClass, int levelIndex, int level) { + cClass = getCharacterClassType(cClass, levelIndex); + if (cClass == -1) + return 0xFFFFFFFF; + + const uint32 *tbl = _expRequirementTables[cClass]; + return tbl[level - 1]; +} + +void EoBCoreEngine::increaseCharacterLevel(int charIndex, int levelIndex) { + _characters[charIndex].level[levelIndex]++; + int hpInc = generateCharacterHitpointsByLevel(charIndex, 1 << levelIndex); + _characters[charIndex].hitPointsCur += hpInc; + _characters[charIndex].hitPointsMax += hpInc; + + gui_drawCharPortraitWithStats(charIndex); + _txt->printMessage(_levelGainStrings[0], -1, _characters[charIndex].name); + snd_playSoundEffect(23); +} + +void EoBCoreEngine::setWeaponSlotStatus(int charIndex, int mode, int slot) { + if (mode == 0 || mode == 2) + _characters[charIndex].disabledSlots ^= (1 << slot); + else if (mode != 1) + return; + + _characters[charIndex].slotStatus[slot] = 0; + gui_drawCharPortraitWithStats(charIndex); +} + +void EoBCoreEngine::setupDialogueButtons(int presetfirst, int numStr, va_list &args) { + _dialogueNumButtons = numStr; + _dialogueHighlightedButton = 0; + + Screen::FontId of = _screen->setFont((_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? Screen::FID_8_FNT : _screen->_currentFont); + + for (int i = 0; i < numStr; i++) { + const char *s = va_arg(args, const char *); + if (s) + _dialogueButtonString[i] = s; + else + _dialogueNumButtons = numStr = i; + } + + static const uint16 prsX[] = { 59, 166, 4, 112, 220, 4, 112, 220, 4, 112, 220, 4, 112, 220 }; + static const uint8 prsY[] = { 0, 0, 0, 0, 0, 12, 12, 12, 24, 24, 24, 36, 36, 36 }; + + const ScreenDim *dm = screen()->_curDim; + int yOffs = (_txt->lineCount() + 1) * _screen->getFontHeight() + dm->sy + 4; + + _dialogueButtonPosX = &prsX[presetfirst]; + _dialogueButtonPosY = &prsY[presetfirst]; + _dialogueButtonYoffs = yOffs; + + drawDialogueButtons(); + + _screen->setFont(of); + + if (!shouldQuit()) + removeInputTop(); +} + +void EoBCoreEngine::initDialogueSequence() { + _npcSequenceSub = -1; + _txt->setWaitButtonMode(0); + _dialogueField = true; + + _dialogueLastBitmap[0] = 0; + + _txt->resetPageBreakString(); + gui_updateControls(); + //_allowSkip = true; + + // WORKAROUND for bug in the original code (all platforms). Sequence sound would be terminated prematurely. + if (_flags.gameID == GI_EOB2 && _currentLevel == 2 && _currentBlock == 654) + _sound->stopAllSoundEffects(); + else + snd_stopSound(); + + Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT"); + _screen->loadFileDataToPage(s, 5, 32000); + _txt->setupField(9, 0); + delete s; +} + +void EoBCoreEngine::restoreAfterDialogueSequence() { + _txt->allowPageBreak(false); + _dialogueField = false; + + _dialogueLastBitmap[0] = 0; + + gui_restorePlayField(); + //_allowSkip = false; + _screen->setScreenDim(7); + + if (_flags.gameID == GI_EOB2) + snd_playSoundEffect(2); + + _sceneUpdateRequired = true; +} + +void EoBCoreEngine::drawSequenceBitmap(const char *file, int destRect, int x1, int y1, int flags) { + static const uint8 frameX[] = { 1, 0 }; + static const uint8 frameY[] = { 8, 0 }; + static const uint8 frameW[] = { 20, 40 }; + static const uint8 frameH[] = { 96, 121 }; + + int page = ((flags & 2) || destRect) ? 0 : 6; + + if (scumm_stricmp(_dialogueLastBitmap, file)) { + _screen->clearPage(2); + if (!destRect) { + if (!(flags & 1)) { + _screen->loadEoBBitmap("BORDER", 0, 3, 3, 2); + _screen->copyRegion(0, 0, 0, 0, 184, 121, 2, page, Screen::CR_NO_P_CHECK); + } else { + _screen->copyRegion(0, 0, 0, 0, 184, 121, 0, page, Screen::CR_NO_P_CHECK); + } + + if (!page) + _screen->copyRegion(0, 0, 0, 0, 184, 121, 2, 6, Screen::CR_NO_P_CHECK); + } + + _screen->loadEoBBitmap(file, 0, 3, 3, 2); + strcpy(_dialogueLastBitmap, file); + } + + if (flags & 2) + _screen->crossFadeRegion(x1 << 3, y1, frameX[destRect] << 3, frameY[destRect], frameW[destRect] << 3, frameH[destRect], 2, page); + else + _screen->copyRegion(x1 << 3, y1, frameX[destRect] << 3, frameY[destRect], frameW[destRect] << 3, frameH[destRect], 2, page, Screen::CR_NO_P_CHECK); + + if (page == 6) + _screen->copyRegion(0, 0, 0, 0, 184, 121, 6, 0, Screen::CR_NO_P_CHECK); + + _screen->updateScreen(); +} + +int EoBCoreEngine::runDialogue(int dialogueTextId, int numStr, ...) { + if (dialogueTextId != -1) + txt()->printDialogueText(dialogueTextId, 0); + + va_list args; + va_start(args, numStr); + if (numStr > 2) + setupDialogueButtons(2, numStr, args); + else + setupDialogueButtons(0, numStr, args); + va_end(args); + + int res = 0; + while (res == 0 && !shouldQuit()) + res = processDialogue(); + + gui_drawDialogueBox(); + + return res; +} + +void EoBCoreEngine::restParty_displayWarning(const char *str) { + int od = _screen->curDimIndex(); + _screen->setScreenDim(7); + Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); + _screen->setCurPage(0); + + _txt->printMessage(Common::String::format("\r%s\r", str).c_str()); + + _screen->setFont(of); + _screen->setScreenDim(od); +} + +bool EoBCoreEngine::restParty_updateMonsters() { + bool sfxEnabled = _sound->sfxEnabled(); + bool musicEnabled = _sound->musicEnabled(); + _sound->enableSFX(false); + _sound->enableMusic(false); + + for (int i = 0; i < 5; i++) { + _partyResting = true; + Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); + int od = _screen->curDimIndex(); + _screen->setScreenDim(7); + updateMonsters(0); + updateMonsters(1); + timerProcessFlyingObjects(0); + _screen->setScreenDim(od); + _screen->setFont(of); + _partyResting = false; + + for (int ii = 0; ii < 30; ii++) { + if (_monsters[ii].mode == 8) + continue; + if (getBlockDistance(_currentBlock, _monsters[ii].block) >= 2) + continue; + + restParty_displayWarning(_menuStringsRest4[0]); + _sound->enableSFX(sfxEnabled); + _sound->enableMusic(musicEnabled); + return true; + } + } + + _sound->enableSFX(sfxEnabled); + _sound->enableMusic(musicEnabled); + return false; +} + +int EoBCoreEngine::restParty_getCharacterWithLowestHp() { + int lhp = 900; + int res = -1; + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 3)) + continue; + if (_characters[i].hitPointsCur >= _characters[i].hitPointsMax) + continue; + if (_characters[i].hitPointsCur < lhp) { + lhp = _characters[i].hitPointsCur; + res = i; + } + } + + return res + 1; +} + +bool EoBCoreEngine::restParty_checkHealSpells(int charIndex) { + static const uint8 eob1healSpells[] = { 2, 15, 20 }; + static const uint8 eob2healSpells[] = { 3, 16, 20 }; + const uint8 *spells = _flags.gameID == GI_EOB1 ? eob1healSpells : eob2healSpells; + const int8 *list = _characters[charIndex].clericSpells; + + for (int i = 0; i < 80; i++) { + int s = list[i] < 0 ? -list[i] : list[i]; + if (s == spells[0] || s == spells[1] || s == spells[2]) + return true; + } + + return false; +} + +bool EoBCoreEngine::restParty_checkSpellsToLearn() { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 0x43)) + continue; + + if ((getCharacterLevelIndex(2, _characters[i].cClass) != -1 || getCharacterLevelIndex(4, _characters[i].cClass) != -1) && (checkInventoryForItem(i, 30, -1) != -1)) { + for (int ii = 0; ii < 80; ii++) { + if (_characters[i].clericSpells[ii] < 0) + return true; + } + } + + if ((getCharacterLevelIndex(1, _characters[i].cClass) != -1) && (checkInventoryForItem(i, 29, -1) != -1)) { + for (int ii = 0; ii < 80; ii++) { + if (_characters[i].mageSpells[ii] < 0) + return true; + } + } + } + + return false; +} + +bool EoBCoreEngine::restParty_extraAbortCondition() { + return false; +} + +void EoBCoreEngine::delay(uint32 millis, bool, bool) { + while (millis && !shouldQuit() && !(_allowSkip && skipFlag())) { + updateInput(); + uint32 step = MIN(millis, (_tickLength / 5)); + _system->delayMillis(step); + millis -= step; + } +} + +void EoBCoreEngine::displayParchment(int id) { + _txt->setWaitButtonMode(1); + _txt->resetPageBreakString(); + gui_updateControls(); + + if (id >= 0) { + // display text + Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT"); + _screen->loadFileDataToPage(s, 5, 32000); + _screen->set16bitShadingLevel(4); + gui_drawBox(0, 0, 176, 175, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + _screen->set16bitShadingLevel(0); + _txt->setupField(12, 1); + if (_flags.gameID == GI_EOB2) + id++; + _txt->printDialogueText(id, _okStrings[0]); + + } else { + // display bitmap + id = -id - 1; + static const uint8 x[] = { 0, 20, 0 }; + static const uint8 y[] = { 0, 0, 96 }; + drawSequenceBitmap("MAP", 0, x[id], y[id], 0); + + removeInputTop(); + while (!shouldQuit()) { + delay(_tickLength); + if (checkInput(0, false, 0) & 0xFF) + break; + removeInputTop(); + } + removeInputTop(); + } + + restoreAfterDialogueSequence(); +} + +int EoBCoreEngine::countResurrectionCandidates() { + _rrCount = 0; + memset(_rrNames, 0, 10 * sizeof(const char *)); + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (_characters[i].hitPointsCur != -10) + continue; + + _rrNames[_rrCount] = _characters[i].name; + _rrId[_rrCount++] = i; + } + + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + + for (int ii = 0; ii < 27; ii++) { + uint16 inv = _characters[i].inventory[ii]; + if (!inv) + continue; + + if ((_flags.gameID == GI_EOB1 && ((_itemTypes[_items[inv].type].extraProperties & 0x7F) != 8)) || (_flags.gameID == GI_EOB2 && _items[inv].type != 33)) + continue; + + _rrNames[_rrCount] = _npcPreset[_items[inv].value - 1].name; + _rrId[_rrCount++] = -_items[inv].value; + } + } + + if (_itemInHand > 0) { + if ((_flags.gameID == GI_EOB1 && ((_itemTypes[_items[_itemInHand].type].extraProperties & 0x7F) == 8)) || (_flags.gameID == GI_EOB2 && _items[_itemInHand].type == 33)) { + _rrNames[_rrCount] = _npcPreset[_items[_itemInHand].value - 1].name; + _rrId[_rrCount++] = -_items[_itemInHand].value; + } + } + + return _rrCount; +} + +void EoBCoreEngine::seq_portal() { + uint8 *shapes1[5]; + uint8 *shapes2[5]; + uint8 *shapes3[5]; + uint8 *shape0; + + _screen->loadShapeSetBitmap("PORTALA", 5, 3); + + for (int i = 0; i < 5; i++) { + shapes1[i] = _screen->encodeShape(i * 3, 0, 3, 75, false, _cgaMappingDefault); + shapes2[i] = _screen->encodeShape(i * 3, 80, 3, 75, false, _cgaMappingDefault); + shapes3[i] = _screen->encodeShape(15, i * 18, 15, 18, false, _cgaMappingDefault); + } + + shape0 = _screen->encodeShape(30, 0, 8, 77, false, _cgaMappingDefault); + _screen->loadEoBBitmap("PORTALB", _cgaMappingDefault, 5, 3, 2); + + snd_playSoundEffect(33); + snd_playSoundEffect(19); + _screen->copyRegion(24, 0, 24, 0, 144, 104, 2, 5, Screen::CR_NO_P_CHECK); + _screen->copyRegion(24, 0, 24, 0, 144, 104, 0, 2, Screen::CR_NO_P_CHECK); + _screen->drawShape(2, shapes3[0], 28, 9, 0); + _screen->drawShape(2, shapes1[0], 34, 28, 0); + _screen->drawShape(2, shapes2[0], 120, 28, 0); + _screen->drawShape(2, shape0, 56, 27, 0); + _screen->crossFadeRegion(24, 0, 24, 0, 144, 104, 2, 0); + _screen->copyRegion(24, 0, 24, 0, 144, 104, 5, 2, Screen::CR_NO_P_CHECK); + delay(30 * _tickLength); + + for (const int8 *pos = _portalSeq; *pos > -1 && !shouldQuit();) { + int s = *pos++; + _screen->drawShape(0, shapes3[s], 28, 9, 0); + _screen->drawShape(0, shapes1[s], 34, 28, 0); + _screen->drawShape(0, shapes2[s], 120, 28, 0); + + if ((s == 1) && (pos >= _portalSeq + 3)) { + if (*(pos - 3) == 0) { + snd_playSoundEffect(24); + snd_playSoundEffect(86); + } + } + + s = *pos++; + if (s == 0) { + _screen->drawShape(0, shape0, 56, 27, 0); + } else { + s--; + _screen->copyRegion((s % 5) << 6, s / 5 * 77, 56, 27, 64, 77, 2, 0, Screen::CR_NO_P_CHECK); + } + + if (s == 1) + snd_playSoundEffect(31); + else if (s == 3) { + if (*(pos - 2) == 3) + snd_playSoundEffect(90); + } + + _screen->updateScreen(); + delay(2 * _tickLength); + } + + delete[] shape0; + for (int i = 0; i < 5; i++) { + delete[] shapes1[i]; + delete[] shapes2[i]; + delete[] shapes3[i]; + } +} + +bool EoBCoreEngine::checkPassword() { + char answ[20]; + Screen::FontId of = _screen->setFont(Screen::FID_8_FNT); + _screen->copyPage(0, 10); + + _screen->setScreenDim(13); + gui_drawBox(_screen->_curDim->sx << 3, _screen->_curDim->sy, _screen->_curDim->w << 3, _screen->_curDim->h, guiSettings()->colors.frame1, guiSettings()->colors.frame2, -1); + gui_drawBox((_screen->_curDim->sx << 3) + 1, _screen->_curDim->sy + 1, (_screen->_curDim->w << 3) - 2, _screen->_curDim->h - 2, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + _screen->modifyScreenDim(13, _screen->_curDim->sx + 1, _screen->_curDim->sy + 2, _screen->_curDim->w - 2, _screen->_curDim->h - 16); + + for (int i = 0; i < 3; i++) { + _screen->fillRect(_screen->_curDim->sx << 3, _screen->_curDim->sy, ((_screen->_curDim->sx + _screen->_curDim->w) << 3) - 1, (_screen->_curDim->sy + _screen->_curDim->h) - 1, guiSettings()->colors.fill); + int c = rollDice(1, _mnNumWord - 1, -1); + const uint8 *shp = (_mnDef[c << 2] < _numLargeItemShapes) ? _largeItemShapes[_mnDef[c << 2]] : (_mnDef[c << 2] < 15 ? 0 : _smallItemShapes[_mnDef[c << 2] - 15]); + assert(shp); + _screen->drawShape(0, shp, 100, 2, 13); + _screen->printShadedText(Common::String::format(_mnPrompt[0], _mnDef[(c << 2) + 1], _mnDef[(c << 2) + 2]).c_str(), (_screen->_curDim->sx + 1) << 3, _screen->_curDim->sy, _screen->_curDim->unk8, guiSettings()->colors.fill); + memset(answ, 0, 20); + gui_drawBox(76, 100, 133, 14, guiSettings()->colors.frame2, guiSettings()->colors.frame1, -1); + gui_drawBox(77, 101, 131, 12, guiSettings()->colors.frame2, guiSettings()->colors.frame1, -1); + if (_gui->getTextInput(answ, 10, 103, 15, _screen->_curDim->unk8, guiSettings()->colors.fill, 8) < 0) + i = 3; + if (!scumm_stricmp(_mnWord[c], answ)) + break; + else if (i == 2) + return false; + } + + _screen->modifyScreenDim(13, _screen->_curDim->sx - 1, _screen->_curDim->sy - 2, _screen->_curDim->w + 2, _screen->_curDim->h + 16); + _screen->setFont(of); + _screen->copyPage(10, 0); + return true; +} + +Common::String EoBCoreEngine::convertAsciiToSjis(Common::String str) { + if (_flags.platform != Common::kPlatformFMTowns) + return str; + + Common::String n; + const char *src = str.c_str(); + int pos = 0; + for (uint32 i = 0; i < str.size(); ++i) { + if (src[i] & 0x80) { + n.insertChar(src[i++], pos++); + n.insertChar(src[i], pos++); + } else if (src[i] >= 32 && src[i] <= 64) { + n.insertChar(_ascii2SjisTables[1][(src[i] - 32) * 2], pos++); + n.insertChar(_ascii2SjisTables[1][(src[i] - 32) * 2 + 1], pos++); + } else if ((src[i] >= 97 && src[i] <= 122) || (src[i] >= 65 && src[i] <= 90)) { + char c = (src[i] >= 97) ? src[i] - 97 : src[i] - 65; + n.insertChar(_ascii2SjisTables2[0][c * 2], pos++); + n.insertChar(_ascii2SjisTables2[0][c * 2 + 1], pos++); + } + } + + return n; +} + +void EoBCoreEngine::useSlotWeapon(int charIndex, int slotIndex, Item item) { + EoBCharacter *c = &_characters[charIndex]; + int tp = item ? _items[item].type : 0; + + if (c->effectFlags & 0x40) + removeCharacterEffect(10, charIndex, 1); // remove invisibility effect + + int ep = _itemTypes[tp].extraProperties & 0x7F; + int8 inflict = 0; + + if (ep == 1) { + inflict = closeDistanceAttack(charIndex, item); + if (!inflict) + inflict = -1; + snd_playSoundEffect(32); + } else if (ep == 2) { + inflict = thrownAttack(charIndex, slotIndex, item); + } else if (ep == 3) { + inflict = projectileWeaponAttack(charIndex, item); + gui_drawCharPortraitWithStats(charIndex); + } + + if (inflict > 0) { + if (_items[item].flags & 8) { + c->hitPointsCur += inflict; + gui_drawCharPortraitWithStats(charIndex); + } + + if (_items[item].flags & 0x10) + c->inventory[slotIndex] = 0; + + inflictMonsterDamage(&_monsters[_dstMonsterIndex], inflict, true); + } + + c->disabledSlots ^= (1 << slotIndex); + c->slotStatus[slotIndex] = inflict; + + gui_drawCharPortraitWithStats(charIndex); + setCharEventTimer(charIndex, 18, inflict >= -2 ? slotIndex + 2 : slotIndex, 1); +} + +int EoBCoreEngine::closeDistanceAttack(int charIndex, Item item) { + if (charIndex > 1) + return -3; + + uint16 d = calcNewBlockPosition(_currentBlock, _currentDirection); + int r = getClosestMonster(charIndex, d); + + if (r == -1) { + uint8 w = _specialWallTypes[_levelBlockProperties[d].walls[_sceneDrawVarDown]]; + if (w == 0xFF) { + if (_flags.gameID == GI_EOB1) { + _levelBlockProperties[d].walls[_sceneDrawVarDown]++; + _levelBlockProperties[d].walls[_sceneDrawVarDown ^ 2]++; + + } else { + for (int i = 0; i < 4; i++) { + if (_specialWallTypes[_levelBlockProperties[d].walls[i]] == 0xFF) + _levelBlockProperties[d].walls[i]++; + } + } + _sceneUpdateRequired = true; + + } else if ((_flags.gameID == GI_EOB1) || (_flags.gameID == GI_EOB2 && w != 8 && w != 9)) { + return -1; + } + + return (_flags.gameID == GI_EOB2 && ((_itemTypes[_items[item].type].allowedClasses & 4) || !item)) ? -5 : -2; + + } else { + if (_monsters[r].flags & 0x20) { + killMonster(&_monsters[r], 1); + _txt->printMessage(_monsterDustStrings[0]); + return -2; + } + + if (!characterAttackHitTest(charIndex, r, item, 1)) + return -1; + + uint16 flg = 0x100; + + if (isMagicEffectItem(item)) + flg |= 1; + + _dstMonsterIndex = r; + return calcMonsterDamage(&_monsters[r], charIndex, item, 1, flg, 5, 3); + } + + return 0; +} + +int EoBCoreEngine::thrownAttack(int charIndex, int slotIndex, Item item) { + int d = charIndex > 3 ? charIndex - 2 : charIndex; + if (!launchObject(charIndex, item, _currentBlock, _dropItemDirIndex[(_currentDirection << 2) + d], _currentDirection, _items[item].type)) + return 0; + + snd_playSoundEffect(11); + _characters[charIndex].inventory[slotIndex] = 0; + reloadWeaponSlot(charIndex, slotIndex, -1, 0); + _sceneUpdateRequired = true; + return 0; +} + +int EoBCoreEngine::projectileWeaponAttack(int charIndex, Item item) { + int tp = _items[item].type; + + if (_flags.gameID == GI_EOB1) + assert(tp >= 7); + + int t = _projectileWeaponAmmoTypes[_flags.gameID == GI_EOB1 ? tp - 7 : tp]; + Item ammoItem = 0; + + if (t == 16) { + if (_characters[charIndex].inventory[0] && _items[_characters[charIndex].inventory[0]].type == 16) + SWAP(ammoItem, _characters[charIndex].inventory[0]); + else if (_characters[charIndex].inventory[1] && _items[_characters[charIndex].inventory[1]].type == 16) + SWAP(ammoItem, _characters[charIndex].inventory[1]); + else if (_characters[charIndex].inventory[16]) + ammoItem = getQueuedItem(&_characters[charIndex].inventory[16], 0, -1); + + } else { + for (int i = 0; i < 27; i++) { + if (_items[_characters[charIndex].inventory[i]].type == t) { + SWAP(ammoItem, _characters[charIndex].inventory[i]); + if (i < 2) + gui_drawCharPortraitWithStats(charIndex); + break; + } + } + } + + if (!ammoItem) + return -4; + + int c = charIndex; + if (c > 3) + c -= 2; + + if (launchObject(charIndex, ammoItem, _currentBlock, _dropItemDirIndex[(_currentDirection << 2) + c], _currentDirection, tp)) { + snd_playSoundEffect(tp == 7 ? 26 : 11); + _sceneUpdateRequired = true; + } + + return 0; +} + +void EoBCoreEngine::inflictMonsterDamage(EoBMonsterInPlay *m, int damage, bool giveExperience) { + m->hitPointsCur -= damage; + m->flags = (m->flags & 0xF7) | 1; + + if (_monsterProps[m->type].capsFlags & 0x2000) { + explodeMonster(m); + checkSceneUpdateNeed(m->block); + m->hitPointsCur = 0; + } else { + if (checkSceneUpdateNeed(m->block)) { + m->flags |= 2; + if (_preventMonsterFlash) + return; + flashMonsterShape(m); + } + } + + if (m->hitPointsCur <= 0) + killMonster(m, giveExperience); + else if (getBlockDistance(m->block, _currentBlock) < 4) + m->dest = _currentBlock; +} + +void EoBCoreEngine::calcAndInflictMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect) { + int dmg = calcMonsterDamage(m, times, pips, offs, flags, savingThrowType, savingThrowEffect); + if (dmg > 0) + inflictMonsterDamage(m, dmg, flags & 0x800 ? true : false); +} + +void EoBCoreEngine::calcAndInflictCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int savingThrowEffect) { + int dmg = calcCharacterDamage(charIndex, times, itemOrPips, useStrModifierOrBase, flags, savingThrowType, savingThrowEffect); + if (dmg) + inflictCharacterDamage(charIndex, dmg); +} + +int EoBCoreEngine::calcCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int savingThrowEffect) { + int s = (flags & 0x100) ? calcDamageModifers(times, 0, itemOrPips, _items[itemOrPips].type, useStrModifierOrBase) : rollDice(times, itemOrPips, useStrModifierOrBase); + EoBCharacter *c = &_characters[charIndex]; + + if (savingThrowType != 5) { + if (trySavingThrow(c, _charClassModifier[c->cClass], c->level[0], savingThrowType, c->raceSex >> 1 /*fix bug in original code by adding a right shift*/)) + s = savingThrowReduceDamage(savingThrowEffect, s); + } + + if ((flags & 0x110) == 0x110) { + if (!calcDamageCheckItemType(_items[itemOrPips].type)) + s = 1; + } + + if (flags & 4) { + if (checkInventoryForRings(charIndex, 3)) + s = 0; + } + + if (flags & 0x400) { + if (c->effectFlags & 0x2000) + s = 0; + else + _txt->printMessage(_characterStatusStrings8[0], -1, c->name); + } + + return s; +} + +void EoBCoreEngine::inflictCharacterDamage(int charIndex, int damage) { + EoBCharacter *c = &_characters[charIndex]; + if (!testCharacter(charIndex, 3)) + return; + + if (c->effectsRemainder[3]) + c->effectsRemainder[3] = (damage < c->effectsRemainder[3]) ? (c->effectsRemainder[3] - damage) : 0; + + c->hitPointsCur -= damage; + c->damageTaken = damage; + + if (c->hitPointsCur > -10) { + snd_playSoundEffect(21); + } else { + c->hitPointsCur = -10; + c->flags &= 1; + c->food = 0; + removeAllCharacterEffects(charIndex); + snd_playSoundEffect(22); + } + + if (c->effectsRemainder[0]) { + c->effectsRemainder[0] = (damage < c->effectsRemainder[0]) ? (c->effectsRemainder[0] - damage) : 0; + if (!c->effectsRemainder[0]) + removeCharacterEffect(1, charIndex, 1); + } + + if (_currentControlMode) + gui_drawFaceShape(charIndex); + else + gui_drawCharPortraitWithStats(charIndex); + + if (c->hitPointsCur <= 0 && _updateFlags == 1 && charIndex == _openBookChar) { + Button b; + clickedSpellbookAbort(&b); + } + + setCharEventTimer(charIndex, 18, 6, 1); +} + +bool EoBCoreEngine::characterAttackHitTest(int charIndex, int monsterIndex, int item, int attackType) { + if (charIndex < 0) + return true; + + int p = item ? (_flags.gameID == GI_EOB1 ? _items[item].type : (_itemTypes[_items[item].type].extraProperties & 0x7F)) : 0; + + if (_monsters[monsterIndex].flags & 0x20) + return true;// EOB 2 only ? + + int t = _monsters[monsterIndex].type; + int d = (p < 1 || p > 3) ? 0 : _items[item].value; + + if (_flags.gameID == GI_EOB2) { + if ((p > 0 && p < 4) || !item) { + if (((_monsterProps[t].immunityFlags & 0x200) && (d <= 0)) || ((_monsterProps[t].immunityFlags & 0x1000) && (d <= 1))) + return false; + } + } + + d += (attackType ? getStrHitChanceModifier(charIndex) : getDexHitChanceModifier(charIndex)); + + int m = getMonsterAcHitChanceModifier(charIndex, _monsterProps[t].armorClass) - d; + int s = rollDice(1, 20); + + _monsters[monsterIndex].flags |= 1; + + if (_flags.gameID == GI_EOB1) { + if (_partyEffectFlags & 0x30) + s++; + if (_characters[charIndex].effectFlags & 0x40) + s++; + } else if ((_partyEffectFlags & 0x8400) || (_characters[charIndex].effectFlags & 0x1000)) { + s++; + } + + s = CLIP(s, 1, 20); + + return s >= m; +} + +bool EoBCoreEngine::monsterAttackHitTest(EoBMonsterInPlay *m, int charIndex) { + int tp = m->type; + EoBMonsterProperty *p = &_monsterProps[tp]; + + int r = rollDice(1, 20); + if (r != 20) { + // Prot from evil + if (_characters[charIndex].effectFlags & 0x800) + r -= 2; + // blur + if (_characters[charIndex].effectFlags & 0x10) + r -= 2; + // prayer + if (_partyEffectFlags & 0x8000) + r--; + } + + return ((r == 20) || (r >= (p->hitChance - _characters[charIndex].armorClass))); +} + +bool EoBCoreEngine::flyingObjectMonsterHit(EoBFlyingObject *fo, int monsterIndex) { + if (fo->attackerId != -1) { + if (!characterAttackHitTest(fo->attackerId, monsterIndex, fo->item, 0)) + return false; + } + calcAndInflictMonsterDamage(&_monsters[monsterIndex], fo->attackerId, fo->item, 0, (fo->attackerId == -1) ? 0x110 : 0x910, 5, 3); + return true; +} + +bool EoBCoreEngine::flyingObjectPartyHit(EoBFlyingObject *fo) { + int ps = _dscItemPosIndex[(_currentDirection << 2) + (_items[fo->item].pos & 3)]; + bool res = false; + + bool b = ((_currentDirection == fo->direction || _currentDirection == (fo->direction ^ 2)) && ps > 2); + int s = ps << 1; + if (ps > 2) + s += rollDice(1, 2, -1); + + static const int8 charId[] = { 0, -1, 1, -1, 2, 4, 3, 5 }; + + for (int i = 0; i < 2; i++) { + int c = charId[s]; + s ^= 1; + if (!testCharacter(c, 3)) + continue; + calcAndInflictCharacterDamage(c, -1, fo->item, 0, 0x110, 5, 3); + res = true; + if (ps < 2 || b == 0) + break; + } + + return res; +} + +void EoBCoreEngine::monsterCloseAttack(EoBMonsterInPlay *m) { + int first = _monsterCloseAttDstTable1[(_currentDirection << 2) + m->dir] * 12; + int v = (m->pos == 4) ? rollDice(1, 2, -1) : _monsterCloseAttChkTable2[(m->dir << 2) + m->pos]; + if (!v) + first += 6; + + int last = first + 6; + for (int i = first; i < last; i++) { + int c = _monsterCloseAttDstTable2[i]; + if (!testCharacter(c, 3)) + continue; + + // Character Invisibility + if ((_characters[c].effectFlags & 0x140) && (rollDice(1, 20) >= 5)) + continue; + + int dmg = 0; + for (int ii = 0; ii < _monsterProps[m->type].attacksPerRound; ii++) { + if (!monsterAttackHitTest(m, c)) + continue; + dmg += rollDice(_monsterProps[m->type].dmgDc[ii].times, _monsterProps[m->type].dmgDc[ii].pips, _monsterProps[m->type].dmgDc[ii].base); + if (_characters[c].effectsRemainder[1]) { + if (--_characters[c].effectsRemainder[1]) + dmg = 0; + } + } + + if (dmg > 0) { + if ((_monsterProps[m->type].capsFlags & 0x80) && rollDice(1, 4, -1) != 3) { + int slot = rollDice(1, 27, -1); + for (int iii = 0; iii < 27; iii++) { + Item itm = _characters[c].inventory[slot]; + if (!itm || !(_itemTypes[_items[itm].type].extraProperties & 0x80)) { + if (++slot == 27) + slot = 0; + continue; + } + + _characters[c].inventory[slot] = 0; + _txt->printMessage(_ripItemStrings[(_characters[c].raceSex & 1) ^ 1], -1, _characters[c].name); + printFullItemName(itm); + _txt->printMessage(_ripItemStrings[2]); + break; + } + gui_drawCharPortraitWithStats(c); + } + + inflictCharacterDamage(c, dmg); + + if (_monsterProps[m->type].capsFlags & 0x10) { + statusAttack(c, 2, _monsterSpecAttStrings[_flags.gameID == GI_EOB1 ? 3 : 2], 0, 1, 8, 1); + _characters[c].effectFlags &= ~0x2000; + } + + if (_monsterProps[m->type].capsFlags & 0x20) + statusAttack(c, 4, _monsterSpecAttStrings[_flags.gameID == GI_EOB1 ? 4 : 3], 2, 5, 9, 1); + + if (_monsterProps[m->type].capsFlags & 0x8000) + statusAttack(c, 8, _monsterSpecAttStrings[4], 2, 0, 0, 1); + + } + + if (!(_monsterProps[m->type].capsFlags & 0x4000)) + return; + } +} + +void EoBCoreEngine::monsterSpellCast(EoBMonsterInPlay *m, int type) { + launchMagicObject(-1, type, m->block, m->pos, m->dir); + snd_processEnvironmentalSoundEffect(_spells[_magicFlightObjectProperties[type << 2]].sound, m->block); +} + +void EoBCoreEngine::statusAttack(int charIndex, int attackStatusFlags, const char *attackStatusString, int savingThrowType, uint32 effectDuration, int restoreEvent, int noRefresh) { + EoBCharacter *c = &_characters[charIndex]; + if ((c->flags & attackStatusFlags) && noRefresh) + return; + if (!testCharacter(charIndex, 3)) + return; + + if (savingThrowType != 5 && specialAttackSavingThrow(charIndex, savingThrowType)) + return; + + if (attackStatusFlags & 8) { + removeAllCharacterEffects(charIndex); + c->flags = (c->flags & 1) | 8; + } else { + c->flags |= attackStatusFlags; + } + + if ((attackStatusFlags & 0x0C) && (_openBookChar == charIndex) && _updateFlags) { + Button b; + clickedSpellbookAbort(&b); + } + + if (effectDuration) + setCharEventTimer(charIndex, effectDuration * 546, restoreEvent, 1); + + gui_drawCharPortraitWithStats(charIndex); + _txt->printMessage(_characterStatusStrings13[0], -1, c->name, attackStatusString); +} + +int EoBCoreEngine::calcMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect) { + int s = flags & 0x100 ? calcDamageModifers(times, m, pips, _items[pips].type, offs) : rollDice(times, pips, offs); + EoBMonsterProperty *p = &_monsterProps[m->type]; + + if (savingThrowType != 5) { + if (trySavingThrow(m, 0, p->level, savingThrowType, 6)) + s = savingThrowReduceDamage(savingThrowEffect, s); + } + + if ((flags & 0x110) == 0x110) { + if (!calcDamageCheckItemType(_items[pips].type)) + s = 1; + } + + if ((flags & 0x100) && (!(_itemTypes[_items[pips].type].allowedClasses & 4 /* bug in original code ??*/)) + && ((_flags.gameID == GI_EOB2 && (p->immunityFlags & 0x100)) || (_flags.gameID == GI_EOB1 && (p->capsFlags & 4)))) + s >>= 1; + + if (p->immunityFlags & 0x2000) { + if (flags & 0x100) { + if (_items[pips].value < 3) + s >>= 2; + if (_items[pips].value == 3) + s >>= 1; + if (s == 0) + s = _items[pips].value; + + } else { + s >>= 1; + } + } + + if (flags & 1) { + if (tryMonsterAttackEvasion(m)) + s = 0; + } + + if (_flags.gameID == GI_EOB1) + return s; + + static const uint16 damageImmunityFlags[] = { 0x01, 0x10, 0x02, 0x20, 0x80, 0x400, 0x20, 0x800, 0x40, 0x80, 0x400, 0x40 }; + for (int i = 0; i < 12; i += 2) { + if ((flags & damageImmunityFlags[i]) && (p->immunityFlags & damageImmunityFlags[i + 1])) + s = 0; + } + + return s; +} + +int EoBCoreEngine::calcDamageModifers(int charIndex, EoBMonsterInPlay *m, int item, int itemType, int useStrModifier) { + int s = (useStrModifier && (charIndex != -1)) ? getStrDamageModifier(charIndex) : 0; + if (item) { + EoBItemType *p = &_itemTypes[itemType]; + int t = m ? m->type : 0; + s += ((m && (_monsterProps[t].capsFlags & 1)) ? rollDice(p->dmgNumDiceL, p->dmgNumPipsL, p->dmgIncS /* bug in original code ? */) : + rollDice(p->dmgNumDiceS, p->dmgNumPipsS, p->dmgIncS)); + s += _items[item].value; + } else { + s += rollDice(1, 2); + } + + return (s < 0) ? 0 : s; +} + +bool EoBCoreEngine::trySavingThrow(void *target, int hpModifier, int level, int type, int race) { + static const int8 constMod[] = { 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5 }; + + if (type == 5) + return false; + + int s = getSaveThrowModifier(hpModifier, level, type); + if (((race == 3 || race == 5) && (type == 4 || type == 1 || type == 0)) || (race == 4 && (type == 4 || type == 1))) { + EoBCharacter *c = (EoBCharacter *)target; + s -= constMod[c->constitutionCur]; + } + + return rollDice(1, 20) >= s; +} + +bool EoBCoreEngine::specialAttackSavingThrow(int charIndex, int type) { + return trySavingThrow(&_characters[charIndex], _charClassModifier[_characters[charIndex].cClass], _characters[charIndex].level[0], type, _characters[charIndex].raceSex >> 1); +} + +int EoBCoreEngine::getSaveThrowModifier(int hpModifier, int level, int type) { + const uint8 *tbl = _saveThrowTables[hpModifier]; + if (_saveThrowLevelIndex[hpModifier] < level) + level = _saveThrowLevelIndex[hpModifier]; + level /= _saveThrowModDiv[hpModifier]; + level += (_saveThrowModExt[hpModifier] * type); + + return tbl[level]; +} + +bool EoBCoreEngine::calcDamageCheckItemType(int itemType) { + itemType = _itemTypes[itemType].extraProperties & 0x7F; + return (itemType == 2 || itemType == 3) ? true : false; +} + +int EoBCoreEngine::savingThrowReduceDamage(int savingThrowEffect, int damage) { + if (savingThrowEffect == 3) + return 0; + + if (savingThrowEffect == 0 || savingThrowEffect == 1) + return damage >> 1; + + return damage; +} + +bool EoBCoreEngine::tryMonsterAttackEvasion(EoBMonsterInPlay *m) { + return rollDice(1, 100) < _monsterProps[m->type].dmgModifierEvade ? true : false; +} + +int EoBCoreEngine::getStrHitChanceModifier(int charIndex) { + static const int8 strExtLimit[] = { 1, 51, 76, 91, 100 }; + static const int8 strExtMod[] = { 1, 2, 2, 2, 3 }; + static const int8 strMod[] = { -4, -3, -3, -2, -2, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 3, 4, 4, 5, 6, 7 }; + + int r = strMod[_characters[charIndex].strengthCur - 1]; + if (_characters[charIndex].strengthExtCur) { + for (int i = 0; i < 5; i++) { + if (_characters[charIndex].strengthExtCur >= strExtLimit[i]) + r = strExtMod[i]; + } + } + + return r; +} + +int EoBCoreEngine::getStrDamageModifier(int charIndex) { + static const int8 strExtLimit[] = { 1, 51, 76, 91, 100 }; + static const int8 strExtMod[] = { 3, 3, 4, 5, 6 }; + static const int8 strMod[] = { -3, -2, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 7, 8, 9, 10, 11, 12, 14 }; + + int r = strMod[_characters[charIndex].strengthCur - 1]; + if (_characters[charIndex].strengthExtCur) { + for (int i = 0; i < 5; i++) { + if (_characters[charIndex].strengthExtCur >= strExtLimit[i]) + r = strExtMod[i]; + } + } + + return r; +} + +int EoBCoreEngine::getDexHitChanceModifier(int charIndex) { + static const int8 dexMod[] = { -5, -4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 3, 4, 4, 4 }; + return dexMod[_characters[charIndex].dexterityCur - 1]; +} + +int EoBCoreEngine::getMonsterAcHitChanceModifier(int charIndex, int monsterAc) { + int l = _characters[charIndex].level[0] - 1; + int cm = _charClassModifier[_characters[charIndex].cClass]; + + return (20 - ((l / _monsterAcHitChanceTable1[cm]) * _monsterAcHitChanceTable2[cm])) - monsterAc; +} + +void EoBCoreEngine::explodeMonster(EoBMonsterInPlay *m) { + m->flags |= 2; + if (getBlockDistance(m->block, _currentBlock) < 2) { + explodeObject(0, _currentBlock, 2); + for (int i = 0; i < 6; i++) + calcAndInflictCharacterDamage(i, 6, 6, 0, 8, 1, 0); + } else { + explodeObject(0, m->block, 2); + } + m->flags &= ~2; +} + +void EoBCoreEngine::snd_playSong(int track) { + _sound->playTrack(track); +} + +void EoBCoreEngine::snd_playSoundEffect(int track, int volume) { + if ((track < 1) || (_flags.gameID == GI_EOB2 && track > 119) || shouldQuit()) + return; + + _sound->playSoundEffect(track, volume); +} + +void EoBCoreEngine::snd_stopSound() { + _sound->haltTrack(); + _sound->stopAllSoundEffects(); +} + +void EoBCoreEngine::snd_fadeOut() { + _sound->beginFadeOut(); +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/eobcommon.h b/engines/kyra/engine/eobcommon.h new file mode 100644 index 0000000000..9e0fdf08b7 --- /dev/null +++ b/engines/kyra/engine/eobcommon.h @@ -0,0 +1,1185 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_EOBCOMMON_H +#define KYRA_EOBCOMMON_H + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) +#include "kyra/engine/kyra_rpg.h" +#endif // (ENABLE_EOB || ENABLE_LOL) + +#ifdef ENABLE_EOB + +namespace Kyra { + +struct DarkMoonShapeDef { + int16 index; + uint8 x, y, w, h; +}; + +struct CreatePartyModButton { + uint8 encodeLabelX; + uint8 encodeLabelY; + uint8 labelW; + uint8 labelH; + uint8 labelX; + uint8 labelY; + uint8 bodyIndex; + uint8 destX; + uint8 destY; +}; + +struct EoBRect8 { + uint8 x; + uint8 y; + uint8 w; + uint8 h; +}; + +struct EoBChargenButtonDef { + uint8 x; + uint8 y; + uint8 w; + uint8 h; + uint8 keyCode; +}; + +struct EoBGuiButtonDef { + uint16 keyCode; + uint16 keyCode2; + uint16 flags; + uint16 x; + uint8 y; + uint16 w; + uint8 h; + uint16 arg; +}; + +struct EoBCharacter { + uint8 id; + uint8 flags; + char name[21]; + int8 strengthCur; + int8 strengthMax; + int8 strengthExtCur; + int8 strengthExtMax; + int8 intelligenceCur; + int8 intelligenceMax; + int8 wisdomCur; + int8 wisdomMax; + int8 dexterityCur; + int8 dexterityMax; + int8 constitutionCur; + int8 constitutionMax; + int8 charismaCur; + int8 charismaMax; + int16 hitPointsCur; + int16 hitPointsMax; + int8 armorClass; + uint8 disabledSlots; + uint8 raceSex; + uint8 cClass; + uint8 alignment; + int8 portrait; + uint8 food; + uint8 level[3]; + uint32 experience[3]; + uint8 *faceShape; + + int8 mageSpells[80]; + int8 clericSpells[80]; + uint32 mageSpellsAvailableFlags; + + Item inventory[27]; + uint32 timers[10]; + int8 events[10]; + uint8 effectsRemainder[4]; + uint32 effectFlags; + uint8 damageTaken; + int8 slotStatus[5]; +}; + +struct EoBItem { + uint8 nameUnid; + uint8 nameId; + uint8 flags; + int8 icon; + int8 type; + int8 pos; + int16 block; + Item next; + Item prev; + uint8 level; + int8 value; +}; + +struct EoBItemType { + uint16 invFlags; + uint16 handFlags; + int8 armorClass; + int8 allowedClasses; + int8 requiredHands; + int8 dmgNumDiceS; + int8 dmgNumPipsS; + int8 dmgIncS; + int8 dmgNumDiceL; + int8 dmgNumPipsL; + int8 dmgIncL; + uint8 unk1; + uint16 extraProperties; +}; + +struct SpriteDecoration { + uint8 *shp; + int16 x; + int16 y; +}; + +struct EoBMonsterProperty { + int8 armorClass; + int8 hitChance; + int8 level; + uint8 hpDcTimes; + uint8 hpDcPips; + uint8 hpDcBase; + uint8 attacksPerRound; + struct DmgDc { + uint8 times; + uint8 pips; + int8 base; + } dmgDc[3]; + uint16 immunityFlags; + uint32 capsFlags; + uint32 typeFlags; + int32 experience; + + uint8 u30; + int8 sound1; + int8 sound2; + uint8 numRemoteAttacks; + uint8 remoteWeaponChangeMode; + uint8 numRemoteWeapons; + + int8 remoteWeapons[5]; + + int8 tuResist; + uint8 dmgModifierEvade; + + uint8 decorations[3]; +}; + +struct EoBMonsterInPlay { + uint8 type; + uint8 unit; + uint16 block; + uint8 pos; + int8 dir; + uint8 animStep; + uint8 shpIndex; + int8 mode; + int8 f_9; + int8 curAttackFrame; + int8 spellStatusLeft; + int16 hitPointsMax; + int16 hitPointsCur; + uint16 dest; + uint16 randItem; + uint16 fixedItem; + uint8 flags; + uint8 idleAnimState; + uint8 curRemoteWeapon; + uint8 numRemoteAttacks; + int8 palette; + uint8 directionChanged; + uint8 stepsTillRemoteAttack; + uint8 sub; +}; + +struct ScriptTimer { + uint16 func; + uint16 ticks; + uint32 next; +}; + +struct EoBMenuDef { + int8 titleStrId; + uint8 dim; + uint8 firstButtonStrId; + int8 numButtons; + int8 titleCol; +}; +struct EoBMenuButtonDef { + int8 labelId; + int16 x; + int8 y; + uint8 width; + uint8 height; + int16 keyCode; + int16 flags; +}; + +class EoBInfProcessor; + +class EoBCoreEngine : public KyraRpgEngine { +friend class TextDisplayer_rpg; +friend class GUI_EoB; +friend class Debugger_EoB; +friend class EoBInfProcessor; +friend class DarkmoonSequenceHelper; +friend class CharacterGenerator; +friend class TransferPartyWiz; +public: + EoBCoreEngine(OSystem *system, const GameFlags &flags); + virtual ~EoBCoreEngine(); + + virtual void initKeymap(); + + Screen *screen() { return _screen; } + GUI *gui() const { return _gui; } + +protected: + // Startup + virtual Common::Error init(); + Common::Error go(); + + // Main Menu, Intro, Finale + virtual int mainMenu() = 0; + virtual void seq_xdeath() {} + virtual void seq_playFinale() = 0; + bool _playFinale; + + //Init, config + void loadItemsAndDecorationsShapes(); + void releaseItemsAndDecorationsShapes(); + + void initButtonData(); + void initMenus(); + void initStaticResource(); + virtual void initSpells(); + + void registerDefaultSettings(); + void readSettings(); + void writeSettings(); + + const uint8 **_largeItemShapes; + const uint8 **_smallItemShapes; + const uint8 **_thrownItemShapes; + const int _numLargeItemShapes; + const int _numSmallItemShapes; + const int _numThrownItemShapes; + const int _numItemIconShapes; + + const uint8 **_spellShapes; + const uint8 **_firebeamShapes; + const uint8 *_redSplatShape; + const uint8 *_greenSplatShape; + const uint8 **_wallOfForceShapes; + const uint8 **_teleporterShapes; + const uint8 **_sparkShapes; + const uint8 *_deadCharShape; + const uint8 *_disabledCharGrid; + const uint8 *_blackBoxSmallGrid; + const uint8 *_weaponSlotGrid; + const uint8 *_blackBoxWideGrid; + const uint8 *_lightningColumnShape; + + uint8 *_itemsOverlay; + static const uint8 _itemsOverlayCGA[]; + + static const uint8 _teleporterShapeDefs[]; + static const uint8 _wallOfForceShapeDefs[]; + + const char *const *_mainMenuStrings; + + // Main loop + virtual void startupNew(); + virtual void startupLoad() = 0; + void runLoop(); + void update() { screen()->updateScreen(); } + bool checkPartyStatus(bool handleDeath); + + bool _runFlag; + + // Character generation / party transfer + bool startCharacterGeneration(); + bool startPartyTransfer(); + + uint8 **_faceShapes; + + static const int8 _characterClassType[]; + static const uint8 _hpIncrPerLevel[]; + static const uint8 _numLevelsPerClass[]; + static const int16 _hpConstModifiers[]; + static const uint8 _charClassModifier[]; + + const uint8 *_classModifierFlags; + + // timers + void setupTimers(); + void setCharEventTimer(int charIndex, uint32 countdown, int evnt, int updateExistingTimer); + void deleteCharEventTimer(int charIndex, int evnt); + void setupCharacterTimers(); + void advanceTimers(uint32 millis); + + void timerProcessMonsters(int timerNum); + void timerSpecialCharacterUpdate(int timerNum); + void timerProcessFlyingObjects(int timerNum); + void timerProcessCharacterExchange(int timerNum); + void timerUpdateTeleporters(int timerNum); + void timerUpdateFoodStatus(int timerNum); + void timerUpdateMonsterIdleAnim(int timerNum); + + uint8 getClock2Timer(int index) { return index < _numClock2Timers ? _clock2Timers[index] : 0; } + uint8 getNumClock2Timers() { return _numClock2Timers; } + + static const uint8 _clock2Timers[]; + static const uint8 _numClock2Timers; + + int32 _restPartyElapsedTime; + + // Mouse + void setHandItem(Item itemIndex); + + // Characters + int getDexterityArmorClassModifier(int dexterity); + int generateCharacterHitpointsByLevel(int charIndex, int levelIndex); + int getClassAndConstHitpointsModifier(int cclass, int constitution); + int getCharacterClassType(int cclass, int levelIndex); + int getModifiedHpLimits(int hpModifier, int constModifier, int level, bool mode); + Common::String getCharStrength(int str, int strExt); + int testCharacter(int16 index, int flags); + int getNextValidCharIndex(int curCharIndex, int searchStep); + + void recalcArmorClass(int index); + int validateWeaponSlotItem(int index, int slot); + + int getClericPaladinLevel(int index); + int getMageLevel(int index); + int getCharacterLevelIndex(int type, int cClass); + + int countCharactersWithSpecificItems(int16 itemType, int16 itemValue); + int checkInventoryForItem(int character, int16 itemType, int16 itemValue); + void modifyCharacterHitpoints(int character, int16 points); + void neutralizePoison(int character); + + void npcSequence(int npcIndex); + virtual void drawNpcScene(int npcIndex) = 0; + virtual void runNpcDialogue(int npcIndex) = 0; + void initNpc(int npcIndex); + int npcJoinDialogue(int npcIndex, int queryJoinTextId, int confirmJoinTextId, int noJoinTextId); + int prepareForNewPartyMember(int16 itemType, int16 itemValue); + void dropCharacter(int charIndex); + void removeCharacterFromParty(int charIndex); + void exchangeCharacters(int charIndex1, int charIndex2); + + void increasePartyExperience(int16 points); + void increaseCharacterExperience(int charIndex, int32 points); + uint32 getRequiredExperience(int cClass, int levelIndex, int level); + void increaseCharacterLevel(int charIndex, int levelIndex); + + void setWeaponSlotStatus(int charIndex, int mode, int slot); + + EoBCharacter *_characters; + Common::String _strenghtStr; + int _castScrollSlot; + int _exchangeCharacterId; + + const char *const *_levelGainStrings; + const uint32 *_expRequirementTables[6]; + + const uint8 *_saveThrowTables[6]; + const uint8 *_saveThrowLevelIndex; + const uint8 *_saveThrowModDiv; + const uint8 *_saveThrowModExt; + + const EoBCharacter *_npcPreset; + int _npcSequenceSub; + bool _partyResting; + bool _loading; + + // Items + void loadItemDefs(); + Item duplicateItem(Item itemIndex); + void setItemPosition(Item *itemQueue, int block, Item item, int pos); + Item createItemOnCurrentBlock(Item itemIndex); + void createInventoryItem(EoBCharacter *c, Item itemIndex, int16 itemValue, int preferedInventorySlot); + int deleteInventoryItem(int charIndex, int slot); + void deleteBlockItem(uint16 block, int type); + int validateInventorySlotForItem(Item item, int charIndex, int slot); + int stripPartyItems(int16 itemType, int16 itemValue, int handleValueMode, int numItems); + bool deletePartyItems(int16 itemType, int16 itemValue); + virtual void updateUsedCharacterHandItem(int charIndex, int slot) = 0; + int itemUsableByCharacter(int charIndex, Item item); + int countQueuedItems(Item itemQueue, int16 id, int16 type, int count, int includeFlyingItems); + int getQueuedItem(Item *items, int pos, int id); + void printFullItemName(Item item); + void identifyQueuedItems(Item itemQueue); + void drawItemIconShape(int pageNum, Item itemId, int x, int y); + bool isMagicEffectItem(Item itemIndex); + bool checkInventoryForRings(int charIndex, int itemValue); + void eatItemInHand(int charIndex); + + bool launchObject(int charIndex, Item item, uint16 startBlock, int startPos, int dir, int type); + void launchMagicObject(int charIndex, int type, uint16 startBlock, int startPos, int dir); + bool updateObjectFlight(EoBFlyingObject *fo, int block, int pos); + bool updateFlyingObjectHitTest(EoBFlyingObject *fo, int block, int pos); + void explodeObject(EoBFlyingObject *fo, int block, Item item); + void endObjectFlight(EoBFlyingObject *fo); + void checkFlyingObjects(); + + void reloadWeaponSlot(int charIndex, int slotIndex, int itemType, int arrowOrDagger); + + EoBItem *_items; + uint16 _numItems; + EoBItemType *_itemTypes; + char **_itemNames; + uint16 _numItemNames; + uint32 _partyEffectFlags; + Item _lastUsedItem; + + const uint16 *_slotValidationFlags; + const int8 *_projectileWeaponAmmoTypes; + const uint8 *_wandTypes; + + EoBFlyingObject *_flyingObjects; + const uint8 *_drawObjPosIndex; + const uint8 *_flightObjFlipIndex; + const int8 *_flightObjShpMap; + const int8 *_flightObjSclIndex; + + const uint8 *_expObjectTlMode; + const uint8 *_expObjectTblIndex; + const uint8 *_expObjectShpStart; + const uint8 *_expObjectAnimTbl1; + int _expObjectAnimTbl1Size; + const uint8 *_expObjectAnimTbl2; + int _expObjectAnimTbl2Size; + const uint8 *_expObjectAnimTbl3; + int _expObjectAnimTbl3Size; + + const char *const *_ascii2SjisTables; + const char *const *_ascii2SjisTables2; + + // Monsters + void loadMonsterShapes(const char *filename, int monsterIndex, bool hasDecorations, int encodeTableIndex); + void releaseMonsterShapes(int first, int num); + uint8 *loadTownsShape(Common::SeekableReadStream *stream); + virtual void generateMonsterPalettes(const char *file, int16 monsterIndex) {} + virtual void loadMonsterDecoration(Common::SeekableReadStream *stream, int16 monsterIndex) {} + const uint8 *loadMonsterProperties(const uint8 *data); + const uint8 *loadActiveMonsterData(const uint8 *data, int level); + void initMonster(int index, int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int i, int randItem, int fixedItem); + void placeMonster(EoBMonsterInPlay *m, uint16 block, int dir); + virtual void replaceMonster(int b, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem) = 0; + void killMonster(EoBMonsterInPlay *m, bool giveExperience); + virtual bool killMonsterExtra(EoBMonsterInPlay *m) = 0; + int countSpecificMonsters(int type); + void updateAttackingMonsterFlags(); + + const int8 *getMonstersOnBlockPositions(uint16 block); + int getClosestMonster(int charIndex, int block); + + bool blockHasMonsters(uint16 block); + bool isMonsterOnPos(EoBMonsterInPlay *m, uint16 block, int pos, int checkPos4); + const int16 *findBlockMonsters(uint16 block, int pos, int dir, int blockDamage, int singleTargetCheckAdjacent); + + void drawBlockObject(int flipped, int page, const uint8 *shape, int x, int y, int sd, uint8 *ovl = 0); + void drawMonsterShape(const uint8 *shape, int x, int y, int flipped, int flags, int palIndex); + void flashMonsterShape(EoBMonsterInPlay *m); + void updateAllMonsterShapes(); + void drawBlockItems(int index); + void drawDoor(int index); + virtual void drawDoorIntern(int type, int index, int x, int y, int w, int wall, int mDim, int16 y1, int16 y2) = 0; + void drawMonsters(int index); + void drawWallOfForce(int index); + void drawFlyingObjects(int index); + void drawTeleporter(int index); + + void updateMonsters(int unit); + void updateMonsterDest(EoBMonsterInPlay *m); + void updateMonsterAttackMode(EoBMonsterInPlay *m); + void updateAllMonsterDests(); + void turnFriendlyMonstersHostile(); + int getNextMonsterDirection(int curBlock, int destBlock); + int getNextMonsterPos(EoBMonsterInPlay *m, int block); + int findFreeMonsterPos(int block, int size); + void updateMoveMonster(EoBMonsterInPlay *m); + bool updateMonsterTryDistanceAttack(EoBMonsterInPlay *m); + bool updateMonsterTryCloseAttack(EoBMonsterInPlay *m, int block); + void walkMonster(EoBMonsterInPlay *m, int destBlock); + bool walkMonsterNextStep(EoBMonsterInPlay *m, int destBlock, int direction); + void updateMonsterFollowPath(EoBMonsterInPlay *m, int turnSteps); + void updateMonstersStraying(EoBMonsterInPlay *m, int a); + void updateMonstersSpellStatus(EoBMonsterInPlay *m); + void setBlockMonsterDirection(int block, int dir); + + uint8 *_monsterFlashOverlay; + uint8 *_monsterStoneOverlay; + + SpriteDecoration *_monsterDecorations; + EoBMonsterProperty *_monsterProps; + + EoBMonsterInPlay *_monsters; + + const int8 *_monsterStepTable0; + const int8 *_monsterStepTable1; + const int8 *_monsterStepTable2; + const int8 *_monsterStepTable3; + const uint8 *_monsterCloseAttPosTable1; + const uint8 *_monsterCloseAttPosTable2; + const int8 *_monsterCloseAttUnkTable; + const uint8 *_monsterCloseAttChkTable1; + const uint8 *_monsterCloseAttChkTable2; + const uint8 *_monsterCloseAttDstTable1; + const uint8 *_monsterCloseAttDstTable2; + + const uint8 *_monsterProximityTable; + const uint8 *_findBlockMonstersTable; + const char *const *_monsterDustStrings; + + const uint8 *_enemyMageSpellList; + const uint8 *_enemyMageSfx; + const uint8 *_beholderSpellList; + const uint8 *_beholderSfx; + const char *const *_monsterSpecAttStrings; + + const int8 *_monsterFrmOffsTable1; + const int8 *_monsterFrmOffsTable2; + + const uint16 *_encodeMonsterShpTable; + const uint8 _teleporterWallId; + + const int16 *_wallOfForceDsX; + const uint8 *_wallOfForceDsY; + const uint8 *_wallOfForceDsNumW; + const uint8 *_wallOfForceDsNumH; + const uint8 *_wallOfForceShpId; + + const int8 *_monsterDirChangeTable; + + // Level + void loadLevel(int level, int sub); + void readLevelFileData(int level); + Common::String initLevelData(int sub); + void addLevelItems(); + void loadVcnData(const char *file, const uint8 *cgaMapping); + void loadBlockProperties(const char *mazFile); + const uint8 *getBlockFileData(int levelIndex = 0); + Common::String getBlockFileName(int levelIndex, int sub); + const uint8 *getBlockFileData(const char *mazFile); + void loadDecorations(const char *cpsFile, const char *decFile); + void assignWallsAndDecorations(int wallIndex, int vmpIndex, int decDataIndex, int specialType, int flags); + void releaseDecorations(); + void releaseDoorShapes(); + void toggleWallState(int wall, int flags); + virtual void loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2) = 0; + virtual const uint8 *loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs) = 0; + + void drawScene(int refresh); + void drawSceneShapes(int start = 0); + void drawDecorations(int index); + + int calcNewBlockPositionAndTestPassability(uint16 curBlock, uint16 direction); + void notifyBlockNotPassable(); + void moveParty(uint16 block); + + int clickedDoorSwitch(uint16 block, uint16 direction); + int clickedDoorPry(uint16 block, uint16 direction); + int clickedDoorNoPry(uint16 block, uint16 direction); + int clickedNiche(uint16 block, uint16 direction); + + int specialWallAction(int block, int direction); + + void openDoor(int block); + void closeDoor(int block); + + int16 _doorType[2]; + int16 _noDoorSwitch[2]; + + EoBRect8 *_levelDecorationRects; + SpriteDecoration *_doorSwitches; + + int8 _currentSub; + Common::String _curGfxFile; + Common::String _curBlockFile; + + uint32 _drawSceneTimer; + uint32 _flashShapeTimer; + uint32 _envAudioTimer; + uint16 _teleporterPulse; + + Common::Array _dscWallMapping; + const int16 *_dscShapeCoords; + + const uint8 *_dscItemPosIndex; + const int16 *_dscItemShpX; + const uint8 *_dscItemScaleIndex; + const uint8 *_dscItemTileIndex; + const uint8 *_dscItemShapeMap; + + const uint8 *_dscDoorScaleMult1; + const uint8 *_dscDoorScaleMult2; + const uint8 *_dscDoorScaleMult3; + const uint8 *_dscDoorY1; + const uint8 *_dscDoorXE; + + uint8 *_greenFadingTable; + uint8 *_blueFadingTable; + uint8 *_lightBlueFadingTable; + uint8 *_blackFadingTable; + uint8 *_greyFadingTable; + + const uint8 *_wllFlagPreset; + int _wllFlagPresetSize; + const uint8 *_teleporterShapeCoords; + const int8 *_portalSeq; + + // Script + void runLevelScript(int block, int flags); + void setScriptFlags(uint32 flags); + void clearScriptFlags(uint32 flags); + bool checkScriptFlags(uint32 flags); + + const uint8 *initScriptTimers(const uint8 *pos); + void updateScriptTimers(); + virtual void updateScriptTimersExtra() {} + + EoBInfProcessor *_inf; + int _stepCounter; + int _stepsUntilScriptCall; + ScriptTimer _scriptTimers[5]; + int _scriptTimersCount; + uint8 _scriptTimersMode; + + // Gui + void gui_drawPlayField(bool refresh); + void gui_restorePlayField(); + void gui_drawAllCharPortraitsWithStats(); + void gui_drawCharPortraitWithStats(int index); + void gui_drawFaceShape(int index); + void gui_drawWeaponSlot(int charIndex, int slot); + void gui_drawWeaponSlotStatus(int x, int y, int status); + void gui_drawHitpoints(int index); + void gui_drawFoodStatusGraph(int index); + void gui_drawHorizontalBarGraph(int x, int y, int w, int h, int32 curVal, int32 maxVal, int col1, int col2); + void gui_drawCharPortraitStatusFrame(int index); + void gui_drawInventoryItem(int slot, int special, int pageNum); + void gui_drawCompass(bool force); + void gui_drawDialogueBox(); + void gui_drawSpellbook(); + void gui_drawSpellbookScrollArrow(int x, int y, int direction); + void gui_updateSlotAfterScrollUse(); + void gui_updateControls(); + void gui_toggleButtons(); + void gui_setPlayFieldButtons(); + void gui_setInventoryButtons(); + void gui_setStatsListButtons(); + void gui_setSwapCharacterButtons(); + void gui_setCastOnWhomButtons(); + void gui_initButton(int index, int x = -1, int y = -1, int val = -1); + Button *gui_getButton(Button *buttonList, int index); + + int clickedInventoryNextPage(Button *button); + int clickedPortraitRestore(Button *button); + int clickedCharPortraitDefault(Button *button); + int clickedCamp(Button *button); + int clickedSceneDropPickupItem(Button *button); + int clickedCharPortrait2(Button *button); + int clickedWeaponSlot(Button *button); + int clickedCharNameLabelRight(Button *button); + int clickedInventorySlot(Button *button); + int clickedEatItem(Button *button); + int clickedInventoryPrevChar(Button *button); + int clickedInventoryNextChar(Button *button); + int clickedSpellbookTab(Button *button); + int clickedSpellbookList(Button *button); + int clickedCastSpellOnCharacter(Button *button); + int clickedUpArrow(Button *button); + int clickedDownArrow(Button *button); + int clickedLeftArrow(Button *button); + int clickedRightArrow(Button *button); + int clickedTurnLeftArrow(Button *button); + int clickedTurnRightArrow(Button *button); + int clickedAbortCharSwitch(Button *button); + int clickedSceneThrowItem(Button *button); + int clickedSceneSpecial(Button *button); + int clickedSpellbookAbort(Button *button); + int clickedSpellbookScroll(Button *button); + int clickedUnk(Button *button); + + void gui_processCharPortraitClick(int index); + void gui_processWeaponSlotClickLeft(int charIndex, int slotIndex); + void gui_processWeaponSlotClickRight(int charIndex, int slotIndex); + void gui_processInventorySlotClick(int slot); + + static const uint8 _buttonList1[]; + int _buttonList1Size; + static const uint8 _buttonList2[]; + int _buttonList2Size; + static const uint8 _buttonList3[]; + int _buttonList3Size; + static const uint8 _buttonList4[]; + int _buttonList4Size; + static const uint8 _buttonList5[]; + int _buttonList5Size; + static const uint8 _buttonList6[]; + int _buttonList6Size; + static const uint8 _buttonList7[]; + int _buttonList7Size; + static const uint8 _buttonList8[]; + int _buttonList8Size; + + EoBGuiButtonDef *_buttonDefs; + + const char *const *_characterGuiStringsHp; + const char *const *_characterGuiStringsWp; + const char *const *_characterGuiStringsWr; + const char *const *_characterGuiStringsSt; + const char *const *_characterGuiStringsIn; + + const char *const *_characterStatusStrings7; + const char *const *_characterStatusStrings8; + const char *const *_characterStatusStrings9; + const char *const *_characterStatusStrings12; + const char *const *_characterStatusStrings13; + + const uint16 *_inventorySlotsX; + const uint8 *_inventorySlotsY; + const uint8 **_compassShapes; + uint8 _charExchangeSwap; + bool _configHpBarGraphs; + bool _configMouseBtSwap; + + Graphics::Surface _thumbNail; + + // text + void setupDialogueButtons(int presetfirst, int numStr, va_list &args); + void initDialogueSequence(); + void restoreAfterDialogueSequence(); + void drawSequenceBitmap(const char *file, int destRect, int x1, int y1, int flags); + int runDialogue(int dialogueTextId, int numStr, ...); + + char _dialogueLastBitmap[13]; + int _moveCounter; + + const char *const *_chargenStatStrings; + const char *const *_chargenRaceSexStrings; + const char *const *_chargenClassStrings; + const char *const *_chargenAlignmentStrings; + + const char *const *_pryDoorStrings; + const char *const *_warningStrings; + + const char *const *_ripItemStrings; + const char *const *_cursedString; + const char *const *_enchantedString; + const char *const *_magicObjectStrings; + const char *const *_magicObjectString5; + const char *const *_patternSuffix; + const char *const *_patternGrFix1; + const char *const *_patternGrFix2; + const char *const *_validateArmorString; + const char *const *_validateCursedString; + const char *const *_validateNoDropString; + const char *const *_potionStrings; + const char *const *_wandStrings; + const char *const *_itemMisuseStrings; + + const char *const *_suffixStringsRings; + const char *const *_suffixStringsPotions; + const char *const *_suffixStringsWands; + + const char *const *_takenStrings; + const char *const *_potionEffectStrings; + + const char *const *_yesNoStrings; + const char *const *_npcMaxStrings; + const char *const *_okStrings; + const char *const *_npcJoinStrings; + const char *const *_cancelStrings; + const char *const *_abortStrings; + + // Rest party + void restParty_displayWarning(const char *str); + bool restParty_updateMonsters(); + int restParty_getCharacterWithLowestHp(); + bool restParty_checkHealSpells(int charIndex); + bool restParty_checkSpellsToLearn(); + virtual void restParty_npc() {} + virtual bool restParty_extraAbortCondition(); + + // misc + void delay(uint32 millis, bool doUpdate = false, bool isMainLoop = false); + + void displayParchment(int id); + int countResurrectionCandidates(); + + void seq_portal(); + bool checkPassword(); + + Common::String convertAsciiToSjis(Common::String str); + + virtual int resurrectionSelectDialogue() = 0; + virtual void useHorn(int charIndex, int weaponSlot) {} + virtual bool checkPartyStatusExtra() = 0; + virtual void drawLightningColumn() {} + virtual int charSelectDialogue() { return -1; } + virtual void characterLevelGain(int charIndex) {} + + Common::Error loadGameState(int slot); + Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail); + + const uint8 *_cgaMappingDefault; + const uint8 *_cgaMappingAlt; + const uint8 *_cgaMappingInv; + const uint8 *_cgaMappingItemsL; + const uint8 *_cgaMappingItemsS; + const uint8 *_cgaMappingThrown; + const uint8 *_cgaMappingIcons; + const uint8 *_cgaMappingDeco; + const uint8 *_cgaMappingLevel[5]; + const uint8 *_cgaLevelMappingIndex; + + bool _enableHiResDithering; + + // Default parameters will import all present original save files and push them to the top of the save dialog. + bool importOriginalSaveFile(int destSlot, const char *sourceFile = 0); + Common::String readOriginalSaveFile(Common::String &file); + bool saveAsOriginalSaveFile(int slot = -1); + + void *generateMonsterTempData(LevelTempData *tmp); + void restoreMonsterTempData(LevelTempData *tmp); + void releaseMonsterTempData(LevelTempData *tmp); + void *generateWallOfForceTempData(LevelTempData *tmp); + void restoreWallOfForceTempData(LevelTempData *tmp); + void releaseWallOfForceTempData(LevelTempData *tmp); + + const char *const *_saveLoadStrings; + + const uint8 *_mnDef; + const char *const *_mnWord; + const char *const *_mnPrompt; + int _mnNumWord; + + int _rrCount; + const char *_rrNames[10]; + int8 _rrId[10]; + + bool _allowSkip; + bool _allowImport; + + Screen_EoB *_screen; + GUI_EoB *_gui; + + // fight + void useSlotWeapon(int charIndex, int slotIndex, Item item); + int closeDistanceAttack(int charIndex, Item item); + int thrownAttack(int charIndex, int slotIndex, Item item); + int projectileWeaponAttack(int charIndex, Item item); + + void inflictMonsterDamage(EoBMonsterInPlay *m, int damage, bool giveExperience); + void calcAndInflictMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect); + void calcAndInflictCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int savingThrowEffect); + int calcCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int damageType); + void inflictCharacterDamage(int charIndex, int damage); + + bool characterAttackHitTest(int charIndex, int monsterIndex, int item, int attackType); + bool monsterAttackHitTest(EoBMonsterInPlay *m, int charIndex); + bool flyingObjectMonsterHit(EoBFlyingObject *fo, int monsterIndex); + bool flyingObjectPartyHit(EoBFlyingObject *fo); + + void monsterCloseAttack(EoBMonsterInPlay *m); + void monsterSpellCast(EoBMonsterInPlay *m, int type); + void statusAttack(int charIndex, int attackStatusFlags, const char *attackStatusString, int savingThrowType, uint32 effectDuration, int restoreEvent, int noRefresh); + + int calcMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect); + int calcDamageModifers(int charIndex, EoBMonsterInPlay *m, int item, int itemType, int useStrModifier); + bool trySavingThrow(void *target, int hpModifier, int level, int type, int race); + bool specialAttackSavingThrow(int charIndex, int type); + int getSaveThrowModifier(int hpModifier, int level, int type); + bool calcDamageCheckItemType(int itemType); + int savingThrowReduceDamage(int savingThrowEffect, int damage); + bool tryMonsterAttackEvasion(EoBMonsterInPlay *m); + int getStrHitChanceModifier(int charIndex); + int getStrDamageModifier(int charIndex); + int getDexHitChanceModifier(int charIndex); + int getMonsterAcHitChanceModifier(int charIndex, int monsterAc); + void explodeMonster(EoBMonsterInPlay *m); + + int _dstMonsterIndex; + bool _preventMonsterFlash; + int16 _foundMonstersArray[5]; + int8 _monsterBlockPosArray[6]; + const uint8 *_monsterAcHitChanceTable1; + const uint8 *_monsterAcHitChanceTable2; + + // magic + void useMagicBookOrSymbol(int charIndex, int type); + void useMagicScroll(int charIndex, int type, int weaponSlot); + void usePotion(int charIndex, int weaponSlot); + void useWand(int charIndex, int weaponSlot); + + virtual void turnUndeadAuto() {} + virtual void turnUndeadAutoHit() {} + + void castSpell(int spell, int weaponSlot); + void removeCharacterEffect(int spell, int charIndex, int showWarning); + void removeAllCharacterEffects(int charIndex); + void castOnWhomDialogue(); + void startSpell(int spell); + + void sparkEffectDefensive(int charIndex); + void sparkEffectOffensive(); + void setSpellEventTimer(int spell, int timerBaseFactor, int timerLength, int timerLevelFactor, int updateExistingTimer); + void sortCharacterSpellList(int charIndex); + + bool magicObjectDamageHit(EoBFlyingObject *fo, int dcTimes, int dcPips, int dcOffs, int level); + bool magicObjectStatusHit(EoBMonsterInPlay *m, int type, bool tryEvade, int mod); + bool turnUndeadHit(EoBMonsterInPlay *m, int hitChance, int casterLevel); + void causeWounds(int dcTimes, int dcPips, int dcOffs); + + int getMagicWeaponSlot(int charIndex); + int createMagicWeaponType(int invFlags, int handFlags, int armorClass, int allowedClasses, int dmgNum, int dmgPips, int dmgInc, int extraProps); + Item createMagicWeaponItem(int flags, int icon, int value, int type); + void removeMagicWeaponItem(Item item); + + void updateWallOfForceTimers(); + void destroyWallOfForce(int index); + + int findSingleSpellTarget(int dist); + + int findFirstCharacterSpellTarget(); + int findNextCharacterSpellTarget(int curCharIndex); + int charDeathSavingThrow(int charIndex, int div); + + void printWarning(const char *str); + void printNoEffectWarning(); + + void spellCallback_start_empty() {} + bool spellCallback_end_empty(void *) { return true; } + void spellCallback_start_armor(); + void spellCallback_start_burningHands(); + void spellCallback_start_detectMagic(); + bool spellCallback_end_detectMagic(void *); + void spellCallback_start_magicMissile(); + bool spellCallback_end_magicMissile(void *obj); + void spellCallback_start_shockingGrasp(); + bool spellCallback_end_shockingGraspFlameBlade(void *obj); + void spellCallback_start_improvedIdentify(); + void spellCallback_start_melfsAcidArrow(); + bool spellCallback_end_melfsAcidArrow(void *obj); + void spellCallback_start_dispelMagic(); + void spellCallback_start_fireball(); + bool spellCallback_end_fireball(void *obj); + void spellCallback_start_flameArrow(); + bool spellCallback_end_flameArrow(void *obj); + void spellCallback_start_holdPerson(); + bool spellCallback_end_holdPerson(void *obj); + void spellCallback_start_lightningBolt(); + bool spellCallback_end_lightningBolt(void *obj); + void spellCallback_start_vampiricTouch(); + bool spellCallback_end_vampiricTouch(void *obj); + void spellCallback_start_fear(); + void spellCallback_start_iceStorm(); + bool spellCallback_end_iceStorm(void *obj); + void spellCallback_start_stoneSkin(); + void spellCallback_start_removeCurse(); + void spellCallback_start_coneOfCold(); + void spellCallback_start_holdMonster(); + bool spellCallback_end_holdMonster(void *obj); + void spellCallback_start_wallOfForce(); + void spellCallback_start_disintegrate(); + void spellCallback_start_fleshToStone(); + void spellCallback_start_stoneToFlesh(); + void spellCallback_start_trueSeeing(); + bool spellCallback_end_trueSeeing(void *); + void spellCallback_start_slayLiving(); + void spellCallback_start_powerWordStun(); + void spellCallback_start_causeLightWounds(); + void spellCallback_start_cureLightWounds(); + void spellCallback_start_aid(); + bool spellCallback_end_aid(void *obj); + void spellCallback_start_flameBlade(); + void spellCallback_start_slowPoison(); + bool spellCallback_end_slowPoison(void *obj); + void spellCallback_start_createFood(); + void spellCallback_start_removeParalysis(); + void spellCallback_start_causeSeriousWounds(); + void spellCallback_start_cureSeriousWounds(); + void spellCallback_start_neutralizePoison(); + void spellCallback_start_causeCriticalWounds(); + void spellCallback_start_cureCriticalWounds(); + void spellCallback_start_flameStrike(); + bool spellCallback_end_flameStrike(void *obj); + void spellCallback_start_raiseDead(); + void spellCallback_start_harm(); + void spellCallback_start_heal(); + void spellCallback_start_layOnHands(); + void spellCallback_start_turnUndead(); + bool spellCallback_end_monster_lightningBolt(void *obj); + bool spellCallback_end_monster_fireball1(void *obj); + bool spellCallback_end_monster_fireball2(void *obj); + bool spellCallback_end_monster_deathSpell(void *obj); + bool spellCallback_end_monster_disintegrate(void *obj); + bool spellCallback_end_monster_causeCriticalWounds(void *obj); + bool spellCallback_end_monster_fleshToStone(void *obj); + + int8 _openBookSpellLevel; + int8 _openBookSpellSelectedItem; + int8 _openBookSpellListOffset; + uint8 _openBookChar; + uint8 _openBookType; + uint8 _openBookCharBackup; + uint8 _openBookTypeBackup; + uint8 _openBookCasterLevel; + const char *const *_openBookSpellList; + int8 *_openBookAvailableSpells; + uint8 _activeSpellCharId; + uint8 _activeSpellCharacterPos; + int _activeSpell; + int _characterSpellTarget; + bool _returnAfterSpellCallback; + + typedef void (EoBCoreEngine::*SpellStartCallback)(); + typedef bool (EoBCoreEngine::*SpellEndCallback)(void *obj); + + struct EoBSpell { + const char *name; + SpellStartCallback startCallback; + uint16 flags; + const uint16 *timingPara; + SpellEndCallback endCallback; + uint8 sound; + uint32 effectFlags; + uint16 damageFlags; + }; + + EoBSpell *_spells; + int _numSpells; + + struct WallOfForce { + uint16 block; + uint32 duration; + }; + + WallOfForce *_wallsOfForce; + + const char *const *_bookNumbers; + const char *const *_mageSpellList; + int _mageSpellListSize; + int _clericSpellOffset; + const char *const *_clericSpellList; + const char *const *_spellNames; + const char *const *_magicStrings1; + const char *const *_magicStrings2; + const char *const *_magicStrings3; + const char *const *_magicStrings4; + const char *const *_magicStrings6; + const char *const *_magicStrings7; + const char *const *_magicStrings8; + + uint8 *_spellAnimBuffer; + + const uint8 *_sparkEffectDefSteps; + const uint8 *_sparkEffectDefSubSteps; + const uint8 *_sparkEffectDefShift; + const uint8 *_sparkEffectDefAdd; + const uint8 *_sparkEffectDefX; + const uint8 *_sparkEffectDefY; + const uint32 *_sparkEffectOfFlags1; + const uint32 *_sparkEffectOfFlags2; + const uint8 *_sparkEffectOfShift; + const uint8 *_sparkEffectOfX; + const uint8 *_sparkEffectOfY; + + const uint8 *_magicFlightObjectProperties; + const uint8 *_turnUndeadEffect; + const uint8 *_burningHandsDest; + const int8 *_coneOfColdDest1; + const int8 *_coneOfColdDest2; + const int8 *_coneOfColdDest3; + const int8 *_coneOfColdDest4; + const uint8 *_coneOfColdGfxTbl; + int _coneOfColdGfxTblSize; + + // Menu + EoBMenuDef *_menuDefs; + const EoBMenuButtonDef *_menuButtonDefs; + + bool _configMouse; + bool _config2431; + + const char *const *_menuStringsMain; + const char *const *_menuStringsSaveLoad; + const char *const *_menuStringsOnOff; + const char *const *_menuStringsSpells; + const char *const *_menuStringsRest; + const char *const *_menuStringsDrop; + const char *const *_menuStringsExit; + const char *const *_menuStringsStarve; + const char *const *_menuStringsScribe; + const char *const *_menuStringsDrop2; + const char *const *_menuStringsHead; + const char *const *_menuStringsPoison; + const char *const *_menuStringsMgc; + const char *const *_menuStringsPrefs; + const char *const *_menuStringsRest2; + const char *const *_menuStringsRest3; + const char *const *_menuStringsRest4; + const char *const *_menuStringsDefeat; + const char *_errorSlotEmptyString; + const char *_errorSlotNoNameString; + const char *_menuOkString; + const char *const *_2431Strings; + const char *const *_katakanaLines; + const char *const *_katakanaSelectStrings; + const char *const *_menuStringsTransfer; + const char *const *_transferStringsScummVM; + const char *const *_menuStringsSpec; + const char *const *_menuStringsSpellNo; + const char *const *_menuYesNoStrings; + const char *const *_saveNamePatterns; + + const uint8 *_spellLevelsMage; + int _spellLevelsMageSize; + const uint8 *_spellLevelsCleric; + int _spellLevelsClericSize; + const uint8 *_numSpellsCleric; + const uint8 *_numSpellsWisAdj; + const uint8 *_numSpellsPal; + const uint8 *_numSpellsMage; + + // sound + void snd_playSong(int id); + void snd_playSoundEffect(int id, int volume=0xFF); + void snd_stopSound(); + void snd_fadeOut(); + + // keymap + static const char *const kKeymapName; +}; + +} // End of namespace Kyra + +#endif // ENABLE_EOB + +#endif diff --git a/engines/kyra/engine/item.h b/engines/kyra/engine/item.h new file mode 100644 index 0000000000..cf06aad8ba --- /dev/null +++ b/engines/kyra/engine/item.h @@ -0,0 +1,41 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_ITEM_H +#define KYRA_ITEM_H + +#include "common/scummsys.h" + +namespace Kyra { + +typedef int16 Item; + +enum { + /** + * Constant for invalid item. + */ + kItemNone = -1 +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/items_eob.cpp b/engines/kyra/engine/items_eob.cpp new file mode 100644 index 0000000000..54d3d5090b --- /dev/null +++ b/engines/kyra/engine/items_eob.cpp @@ -0,0 +1,732 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound.h" + +namespace Kyra { + +void EoBCoreEngine::loadItemDefs() { + Common::SeekableReadStream *s = _res->createReadStream("item.dat"); + memset(_items, 0, sizeof(EoBItem) * 600); + _numItems = s->readUint16LE(); + + for (int i = 0; i < 600; i++) + _items[i].block = -1; + + for (int i = 0; i < _numItems; i++) { + _items[i].nameUnid = s->readByte(); + _items[i].nameId = s->readByte(); + _items[i].flags = s->readByte(); + _items[i].icon = s->readSByte(); + _items[i].type = s->readSByte(); + _items[i].pos = s->readSByte(); + _items[i].block = s->readSint16LE(); + _items[i].next = s->readSint16LE(); + _items[i].prev = s->readSint16LE(); + _items[i].level = s->readSByte(); + _items[i].value = s->readSByte(); + } + + _numItemNames = s->readUint16LE(); + for (int i = 0; i < _numItemNames; i++) + s->read(_itemNames[i], 35); + + delete s; + + s = _res->createReadStream("itemtype.dat"); + uint16 numTypes = s->readUint16LE(); + + delete[] _itemTypes; + _itemTypes = new EoBItemType[65]; + memset(_itemTypes, 0, sizeof(EoBItemType) * 65); + + for (int i = 0; i < numTypes; i++) { + _itemTypes[i].invFlags = s->readUint16LE(); + _itemTypes[i].handFlags = s->readUint16LE(); + _itemTypes[i].armorClass = s->readSByte(); + _itemTypes[i].allowedClasses = s->readSByte(); + _itemTypes[i].requiredHands = s->readSByte(); + _itemTypes[i].dmgNumDiceS = s->readSByte(); + _itemTypes[i].dmgNumPipsS = s->readSByte(); + _itemTypes[i].dmgIncS = s->readSByte(); + _itemTypes[i].dmgNumDiceL = s->readSByte(); + _itemTypes[i].dmgNumPipsL = s->readSByte(); + _itemTypes[i].dmgIncL = s->readSByte(); + _itemTypes[i].unk1 = s->readByte(); + _itemTypes[i].extraProperties = s->readUint16LE(); + } + + delete s; +} + +Kyra::Item EoBCoreEngine::duplicateItem(Item itemIndex) { + EoBItem *itm = &_items[itemIndex]; + + if (itm->block == -1) + return 0; + + Item i = 1; + bool foundSlot = false; + + for (; i < 600; i++) { + if (_items[i].block == -1) { + foundSlot = true; + break; + } + } + + if (!foundSlot) + return 0; + + memcpy(&_items[i], itm, sizeof(EoBItem)); + return i; +} + +Item EoBCoreEngine::createItemOnCurrentBlock(Item itemIndex) { + Item itm = duplicateItem(itemIndex); + setItemPosition((Item *)&_levelBlockProperties[_currentBlock].drawObjects, _currentBlock, itm, _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]); + return itm; +} + +void EoBCoreEngine::setItemPosition(Item *itemQueue, int block, Item item, int pos) { + if (!item) + return; + + EoBItem *itm = &_items[item]; + itm->pos = pos; + itm->block = block; + itm->level = block < 0 ? 0xFF : _currentLevel; + + if (!*itemQueue) { + *itemQueue = itm->next = itm->prev = item; + } else { + EoBItem *itmQ = &_items[*itemQueue]; + EoBItem *itmQN = &_items[itmQ->next]; + itm->prev = itmQN->prev; + itm->next = itmQ->next; + *itemQueue = itmQN->prev = itmQ->next = item; + } +} + +void EoBCoreEngine::createInventoryItem(EoBCharacter *c, Item itemIndex, int16 itemValue, int preferedInventorySlot) { + if (itemIndex <= 0) + return; + + itemIndex = duplicateItem(itemIndex); + _items[itemIndex].flags |= 0x40; + + if (itemValue != -1) + _items[itemIndex].value = itemValue; + + if (itemValue && ((_itemTypes[_items[itemIndex].type].extraProperties & 0x7F) < 4)) + _items[itemIndex].flags |= 0x80; + + if (c->inventory[preferedInventorySlot]) { + for (int i = 2; i < 16; i++) { + if (!c->inventory[i]) { + c->inventory[i] = itemIndex; + return; + } + } + } else { + c->inventory[preferedInventorySlot] = itemIndex; + } +} + +int EoBCoreEngine::deleteInventoryItem(int charIndex, int slot) { + int itm = (slot == -1) ? _itemInHand : _characters[charIndex].inventory[slot]; + _items[itm].block = -1; + + if (slot == -1) { + setHandItem(0); + } else { + _characters[charIndex].inventory[slot] = 0; + + if (_currentControlMode == 1) + gui_drawInventoryItem(slot, 1, 0); + + if (_currentControlMode == 0) + gui_drawCharPortraitWithStats(charIndex); + } + + return _items[itm].value; +} + +void EoBCoreEngine::deleteBlockItem(uint16 block, int type) { + uint16 itm = _levelBlockProperties[block].drawObjects; + if (!itm) + return; + + _levelBlockProperties[block].drawObjects = 0; + + for (uint16 i2 = itm, i = 0; itm != i2 || !i; i++) { + if (type == _items[itm].type || type == -1) { + _items[itm].block = -1; + _items[itm].level = 0; + uint16 i3 = itm; + itm = _items[itm].prev; + _items[i3].prev = _items[i3].next = 0; + } else { + uint16 i3 = itm; + itm = _items[itm].prev; + _items[i3].prev = _items[i3].next = 0; + setItemPosition((Item *)&_levelBlockProperties[block].drawObjects, block, i3, _items[i3].pos); + } + } +} + +int EoBCoreEngine::validateInventorySlotForItem(Item item, int charIndex, int slot) { + if (item < 0) + return 0; + + if (slot == 17 && item && !itemUsableByCharacter(charIndex, item)) { + _txt->printMessage(_validateArmorString[0], -1, _characters[charIndex].name); + return 0; + } + + int itm = _characters[charIndex].inventory[slot]; + int ex = _itemTypes[_items[itm].type].extraProperties & 0x7F; + + if (_items[itm].flags & 0x20 && (_flags.gameID == GI_EOB1 || slot < 2)) { + if (_flags.gameID == GI_EOB2 && ex > 0 && ex < 4) + _txt->printMessage(_validateCursedString[0], -1, _characters[charIndex].name); + return 0; + } + + uint16 v = item ? _itemTypes[_items[item].type].invFlags : 0xFFFF; + if (v & _slotValidationFlags[slot]) + return 1; + + _txt->printMessage(_validateNoDropString[0]); + return 0; +} + +int EoBCoreEngine::stripPartyItems(int16 itemType, int16 itemValue, int handleValueMode, int numItems) { + int itemsLeft = numItems; + + for (bool runloop = true; runloop && itemsLeft;) { + runloop = false; + for (int i = 0; i < 6 && itemsLeft; i++) { + if (!testCharacter(i, 1)) + continue; + + for (int ii = 0; ii < 27 && itemsLeft; ii++) { + if (ii == 16) + continue; + + Item itm = _characters[i].inventory[ii]; + if ((_items[itm].type == itemType) && ((handleValueMode == -1 && _items[itm].value <= itemValue) || (handleValueMode == 0 && _items[itm].value == itemValue) || (handleValueMode == 1 && _items[itm].value >= itemValue))) { + _characters[i].inventory[ii] = 0; + _items[itm].block = -1; + itemsLeft--; + runloop = true; + } + } + } + } + + return numItems - itemsLeft; +} + +bool EoBCoreEngine::deletePartyItems(int16 itemType, int16 itemValue) { + bool res = false; + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + + EoBCharacter *c = &_characters[i]; + for (int slot = checkInventoryForItem(i, itemType, itemValue); slot != -1; slot = checkInventoryForItem(i, itemType, itemValue)) { + int itm = c->inventory[slot]; + _items[itm].block = -1; + c->inventory[slot] = 0; + res = true; + + if (!_dialogueField) { + if (_currentControlMode == 0 && slot < 2 && i < 5) + gui_drawWeaponSlot(i, slot); + + if (_currentControlMode == 1 && i == _updateCharNum) + gui_drawInventoryItem(slot, 1, 0); + } + } + } + + if (_itemInHand > 0) { + if ((itemType == -1 || itemType == _items[_itemInHand].type) && (itemValue == -1 || itemValue == _items[_itemInHand].value)) { + _items[_itemInHand].block = -1; + setHandItem(0); + res = true; + } + } + + return res; +} + +int EoBCoreEngine::itemUsableByCharacter(int charIndex, Item item) { + if (!item) + return 1; + + return (_itemTypes[_items[item].type].allowedClasses & _classModifierFlags[_characters[charIndex].cClass]); +} + +int EoBCoreEngine::countQueuedItems(Item itemQueue, int16 id, int16 type, int count, int includeFlyingItems) { + uint16 o1 = itemQueue; + uint16 o2 = o1; + + if (!o1) + return 0; + + int res = 0; + + for (bool forceLoop = true; o1 != o2 || forceLoop; o1 = _items[o1].prev) { + EoBItem *itm = &_items[o1]; + forceLoop = false; + if (id != -1 || type != -1) { + if (((id != -1) || (id == -1 && type != itm->type)) && ((type != -1) || (type == -1 && id != o1))) + continue; + } + + if (!includeFlyingItems) { + if (itm->pos > 3 && itm->pos < 8) + continue; + } + + if (!count) + return o1; + + res++; + } + + return res; +} + +int EoBCoreEngine::getQueuedItem(Item *items, int pos, int id) { + Item o1 = *items; + Item o2 = o1; + + if (!o1) + return 0; + + EoBItem *itm = &_items[o1]; + + for (bool forceLoop = true; o1 != o2 || forceLoop; o1 = itm->prev) { + itm = &_items[o1]; + forceLoop = false; + if ((id != -1 || (id == -1 && itm->pos != pos)) && id != o1) + continue; + + Item n = itm->next; + Item p = itm->prev; + _items[n].prev = p; + _items[p].next = n; + itm->next = itm->prev = itm->block = 0; + itm->level = 0; + if (o1 == *items) + *items = p; + if (o1 == *items) + *items = 0; + + return o1; + } + + return 0; +} + +void EoBCoreEngine::printFullItemName(Item item) { + EoBItem *itm = &_items[item]; + const char *nameUnid = _itemNames[itm->nameUnid]; + const char *nameId = _itemNames[itm->nameId]; + uint8 f = _itemTypes[itm->type].extraProperties & 0x7F; + int8 v = itm->value; + + const char *tstr2 = 0; + const char *tstr3 = 0; + + bool correctSuffixCase = false; + + Common::String tmpString; + + if ((itm->flags & 0x40) && !strlen(nameId)) { + switch (f) { + case 0: + case 1: + case 2: + case 3: + if (v == 0) + tmpString = nameUnid; + else if (v < 0) + tmpString = _flags.gameID == GI_EOB1 ? Common::String::format(_cursedString[0], nameUnid, v) : Common::String::format(_cursedString[0], v, nameUnid); + else + tmpString = _flags.gameID == GI_EOB1 ? Common::String::format(_enchantedString[0], nameUnid, v) : Common::String::format(_enchantedString[0], v, nameUnid); + break; + + case 9: + tstr2 = _magicObjectStrings[0]; + tstr3 = _spells[v].name; + correctSuffixCase = true; + break; + + case 10: + tstr2 = _magicObjectStrings[1]; + tstr3 = _spells[_flags.gameID == GI_EOB1 ? (_clericSpellOffset + v) : v].name; + correctSuffixCase = true; + break; + + case 14: + tstr2 = _magicObjectStrings[3]; + if (_flags.gameID == GI_EOB1) + v--; + tstr3 = _suffixStringsPotions[v]; + break; + + case 16: + tstr2 = _magicObjectStrings[2]; + tstr3 = _suffixStringsRings[v]; + break; + + case 18: + if (_flags.gameID == GI_EOB2 && v == 5) { + if (_flags.lang == Common::DE_DEU) + tstr2 = _magicObjectString5[0]; + else + tstr3 = _magicObjectString5[0]; + correctSuffixCase = true; + } else { + tstr2 = _magicObjectStrings[4]; + } + tstr3 = _suffixStringsWands[v]; + break; + + default: + tmpString = nameUnid; + break; + } + + + if (tstr3) { + if (!tstr2) { + tmpString = tstr3; + } else { + if (correctSuffixCase) { + if (tstr2 == _magicObjectString5[0]) + tmpString = Common::String::format(_patternGrFix2[0], tstr2, tstr3); + else + tmpString = Common::String::format(_patternGrFix1[0], tstr2, tstr3); + } else { + tmpString = Common::String::format(_patternSuffix[0], tstr2, tstr3); + } + } + } + } else { + tmpString = (itm->flags & 0x40) ? nameId : nameUnid; + } + + _txt->printMessage(convertAsciiToSjis(tmpString).c_str()); +} + +void EoBCoreEngine::identifyQueuedItems(Item itemQueue) { + if (!itemQueue) + return; + + Item first = itemQueue; + do { + _items[itemQueue].flags |= 0x40; + itemQueue = _items[itemQueue].prev; + + } while (first != itemQueue); +} + +void EoBCoreEngine::drawItemIconShape(int pageNum, Item itemId, int x, int y) { + int icn = _items[itemId].icon; + bool applyBluePal = ((_partyEffectFlags & 2) && (_items[itemId].flags & 0x80)) ? true : false; + const uint8 *ovl = 0; + + if (applyBluePal) { + if (_flags.gameID == GI_EOB1) { + ovl = (_configRenderMode == Common::kRenderCGA) ? _itemsOverlayCGA : &_itemsOverlay[icn << 4]; + } else { + _screen->setFadeTable(_lightBlueFadingTable); + _screen->setShapeFadingLevel(1); + } + } + + _screen->drawShape(pageNum, _itemIconShapes[icn], x, y, 0, ovl ? 2 : 0, ovl); + + if (applyBluePal) { + _screen->setFadeTable(_greyFadingTable); + _screen->setShapeFadingLevel(0); + } +} + +bool EoBCoreEngine::isMagicEffectItem(Item itemIndex) { + return (itemIndex > 10 && itemIndex < 18); +} + +bool EoBCoreEngine::checkInventoryForRings(int charIndex, int itemValue) { + for (int i = 25; i <= 26; i++) { + int itm = _characters[charIndex].inventory[i]; + if (itm && _items[itm].type == 47 && _items[itm].value == itemValue) + return true; + } + return false; +} + +void EoBCoreEngine::eatItemInHand(int charIndex) { + EoBCharacter *c = &_characters[charIndex]; + if (!testCharacter(charIndex, 5)) { + _txt->printMessage(_warningStrings[1], -1, c->name); + } else if (_itemInHand && _items[_itemInHand].type != 31 && !(_flags.gameID == GI_EOB1 && _items[_itemInHand].type == 49)) { + _txt->printMessage(_warningStrings[_flags.gameID == GI_EOB1 ? 2 : 3]); + } else if (_items[_itemInHand].value == -1) { + printWarning(_warningStrings[2]); + } else { + c->food += _items[_itemInHand].value; + if (c->food > 100) + c->food = 100; + + _items[_itemInHand].block = -1; + setHandItem(0); + gui_drawFoodStatusGraph(charIndex); + _screen->updateScreen(); + snd_playSoundEffect(9); + } +} + +bool EoBCoreEngine::launchObject(int charIndex, Item item, uint16 startBlock, int startPos, int dir, int type) { + EoBFlyingObject *t = _flyingObjects; + int slot = 0; + for (; slot < 10; slot++) { + if (!t->enable) + break; + t++; + } + + if (slot == 10) + return false; + + setItemPosition((Item *)&_levelBlockProperties[startBlock].drawObjects, startBlock, item, startPos | 4); + + t->enable = 1; + t->starting = 1; + t->flags = 0; + t->direction = dir; + t->distance = 12; + t->curBlock = startBlock; + t->curPos = startPos; + t->item = item; + t->objectType = type; + t->attackerId = charIndex; + t->callBackIndex = 0; + + snd_playSoundEffect(type == 7 ? 26 : 11); + return true; +} + +void EoBCoreEngine::launchMagicObject(int charIndex, int type, uint16 startBlock, int startPos, int dir) { + EoBFlyingObject *t = _flyingObjects; + int slot = 0; + for (; slot < 10; slot++) { + if (!t->enable) + break; + t++; + } + + if (slot == 10) + return; + + t->enable = 2; + t->starting = 1; + t->flags = _magicFlightObjectProperties[(type << 2) + 2]; + t->direction = dir; + t->distance = _magicFlightObjectProperties[(type << 2) + 1]; + t->curBlock = startBlock; + t->curPos = startPos; + t->item = type; + t->objectType = _magicFlightObjectProperties[(type << 2) + 3]; + t->attackerId = charIndex; + t->callBackIndex = _magicFlightObjectProperties[type << 2]; + _sceneUpdateRequired = true; +} + +bool EoBCoreEngine::updateObjectFlight(EoBFlyingObject *fo, int block, int pos) { + uint8 wallFlags = _wllWallFlags[_levelBlockProperties[block].walls[fo->direction ^ 2]]; + if (fo->enable == 1) { + if ((wallFlags & 1) || (fo->starting) || ((wallFlags & 2) && (_dscItemShapeMap[_items[fo->item].icon] >= 15))) { + getQueuedItem((Item *)&_levelBlockProperties[fo->curBlock].drawObjects, 0, fo->item); + setItemPosition((Item *)&_levelBlockProperties[block].drawObjects, block, fo->item, pos | 4); + fo->curBlock = block; + fo->curPos = pos; + fo->distance--; + return true; + + } else { + _clickedSpecialFlag = 0x10; + specialWallAction(block, fo->direction); + return false; + } + + } else { + if (!(wallFlags & 1) && (fo->curBlock != block)) + return false; + fo->curBlock = block; + fo->curPos = pos; + if (fo->distance != 255) + fo->distance--; + } + return true; +} + +bool EoBCoreEngine::updateFlyingObjectHitTest(EoBFlyingObject *fo, int block, int pos) { + if (fo->starting && (fo->curBlock != _currentBlock || fo->attackerId >= 0) && (!blockHasMonsters(fo->curBlock) || fo->attackerId < 0)) + return false; + + if (fo->enable == 2) { + if (fo->callBackIndex) + return (this->*_spells[fo->callBackIndex].endCallback)(fo); + } + + if (blockHasMonsters(block)) { + for (int i = 0; i < 30; i++) { + if (!isMonsterOnPos(&_monsters[i], block, pos, 1)) + continue; + if (flyingObjectMonsterHit(fo, i)) + return true; + } + + } else if (block == _currentBlock) { + return flyingObjectPartyHit(fo); + } + + return false; +} + +void EoBCoreEngine::explodeObject(EoBFlyingObject *fo, int block, Item item) { + if (_partyResting) { + snd_processEnvironmentalSoundEffect(35, _currentBlock); + return; + } + + const uint8 *table = (_expObjectTblIndex[item] == 0) ? _expObjectAnimTbl1 : ((_expObjectTblIndex[item] == 1) ? _expObjectAnimTbl2 : _expObjectAnimTbl3); + int tableSize = (_expObjectTblIndex[item] == 0) ? _expObjectAnimTbl1Size : ((_expObjectTblIndex[item] == 1) ? _expObjectAnimTbl2Size : _expObjectAnimTbl3Size); + + int tl = 0; + for (; tl < 18; tl++) { + if (_visibleBlockIndex[tl] == block) + break; + } + + if (tl == 18) + return; + + int b = _expObjectTlMode ? _expObjectTlMode[tl] : 2; + + uint8 fdr = fo ? fo->direction : 0; + if (b == 0 || (b == 1 && (fdr & 1) == (_currentDirection & 1))) { + snd_processEnvironmentalSoundEffect(35, _currentBlock); + return; + } + + uint8 dm = _dscDimMap[tl]; + int16 x1 = 0; + int16 x2 = 0; + + setLevelShapesDim(tl, x1, x2, 5); + + if (x2 < x1) + return; + + if (fo) + fo->enable = 0; + + drawScene(1); + + if (fo) + fo->enable = 2; + + _screen->fillRect(0, 0, 176, 120, 0, 2); + uint8 col = _screen->getPagePixel(2, 0, 0); + drawSceneShapes(_expObjectShpStart[dm]); + + setLevelShapesDim(tl, x1, x2, 5); + _screen->updateScreen(); + + _screen->setGfxParameters(_dscShapeCoords[(tl * 5 + 4) << 1] + 88, 48, col); + snd_processEnvironmentalSoundEffect(35, _currentBlock); + + disableSysTimer(2); + if (dm == 0) { + _screen->drawExplosion(3, 147, 35, 20, 7, table, tableSize); + } else if (dm == 1) { + _screen->drawExplosion(2, 147, 35, 20, 7, table, tableSize); + } else if (dm == 2) { + _screen->drawExplosion(1, 147, 35, 20, 7, table, tableSize); + } else if (dm == 3) { + _screen->drawExplosion(0, 460, 50, 20, 4, table, tableSize); + } + enableSysTimer(2); +} + +void EoBCoreEngine::endObjectFlight(EoBFlyingObject *fo) { + if (fo->enable == 1) { + _items[fo->item].pos &= 3; + runLevelScript(fo->curBlock, 4); + updateEnvironmentalSfx(18); + } + memset(fo, 0, sizeof(EoBFlyingObject)); +} + +void EoBCoreEngine::checkFlyingObjects() { + if (!_runFlag) + return; + + for (int i = 0; i < 10; i++) { + EoBFlyingObject *fo = &_flyingObjects[i]; + if (!fo->enable) + continue; + if (updateFlyingObjectHitTest(fo, fo->curBlock, fo->curPos)) + endObjectFlight(fo); + } +} + +void EoBCoreEngine::reloadWeaponSlot(int charIndex, int slotIndex, int itemType, int arrowOrDagger) { + if (arrowOrDagger && _characters[charIndex].inventory[16]) { + _characters[charIndex].inventory[slotIndex] = getQueuedItem(&_characters[charIndex].inventory[16], 0, -1); + } else { + for (int i = 24; i >= 22; i--) { + if (!_characters[charIndex].inventory[i]) + continue; + if (_items[_characters[charIndex].inventory[i]].type == itemType && itemType != -1) + continue; + _characters[charIndex].inventory[slotIndex] = _characters[charIndex].inventory[i]; + _characters[charIndex].inventory[i] = 0; + return; + } + } +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/items_hof.cpp b/engines/kyra/engine/items_hof.cpp new file mode 100644 index 0000000000..dd53882cb9 --- /dev/null +++ b/engines/kyra/engine/items_hof.cpp @@ -0,0 +1,424 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_hof.h" + +#include "common/system.h" + +namespace Kyra { + +int KyraEngine_HoF::checkItemCollision(int x, int y) { + int itemPos = -1, yPos = -1; + + for (int i = 0; i < 30; ++i) { + const ItemDefinition &curItem = _itemList[i]; + + if (curItem.id == kItemNone || curItem.sceneId != _mainCharacter.sceneId) + continue; + + int itemX1 = curItem.x - 8 - 3; + int itemX2 = curItem.x + 7 + 3; + + if (x < itemX1 || x > itemX2) + continue; + + int itemY1 = curItem.y - _itemHtDat[curItem.id] - 3; + int itemY2 = curItem.y + 3; + + if (y < itemY1 || y > itemY2) + continue; + + if (curItem.y >= yPos) { + itemPos = i; + yPos = curItem.y; + } + } + + return itemPos; +} + +void KyraEngine_HoF::updateWaterFlasks() { + for (int i = 22; i < 24; i++) { + if (_itemInHand == i) + setHandItem(i - 1); + + for (int ii = 0; ii < 20; ii++) { + if (_mainCharacter.inventory[ii] == i) { + _mainCharacter.inventory[ii]--; + if (ii < 10) { + clearInventorySlot(ii, 0); + _screen->drawShape(0, getShapePtr(i + 63), _inventoryX[ii], _inventoryY[ii], 0, 0); + } + } + } + + for (int ii = 0; ii < 30; ii++) { + if (_itemList[ii].id == i) + _itemList[ii].id--; + } + } +} + +bool KyraEngine_HoF::dropItem(int unk1, Item item, int x, int y, int unk2) { + if (_mouseState <= -1) + return false; + + bool success = processItemDrop(_mainCharacter.sceneId, item, x, y, unk1, unk2); + if (!success) { + snd_playSoundEffect(0x0D); + if (countAllItems() >= 30) + showMessageFromCCode(5, 0x84, 0); + } + + return success; +} + +bool KyraEngine_HoF::processItemDrop(uint16 sceneId, Item item, int x, int y, int unk1, int unk2) { + int itemPos = checkItemCollision(x, y); + + if (unk1) + itemPos = -1; + + if (itemPos >= 0) { + exchangeMouseItem(itemPos); + return false; + } + + int freeItemSlot = -1; + + if (unk1 != 3) { + for (int i = 0; i < 30; ++i) { + if (_itemList[i].id == kItemNone) { + freeItemSlot = i; + break; + } + } + } + + if (freeItemSlot == -1) + return false; + + if (sceneId != _mainCharacter.sceneId) { + _itemList[freeItemSlot].x = x; + _itemList[freeItemSlot].y = y; + _itemList[freeItemSlot].id = item; + _itemList[freeItemSlot].sceneId = sceneId; + return true; + } + + int itemHeight = _itemHtDat[item]; + + // no idea why it's '&&' here and not single checks for x and y + if (x == -1 && y == -1) { + x = _rnd.getRandomNumberRng(0x10, 0x130); + y = _rnd.getRandomNumberRng(0x10, 0x87); + } + + int posX = x, posY = y; + int itemX = -1, itemY = -1; + bool needRepositioning = true; + + while (needRepositioning) { + if ((_screen->getDrawLayer(posX, posY) <= 1 && _screen->getDrawLayer2(posX, posY, itemHeight) <= 1 && isDropable(posX, posY)) || posY == 136) { + int posX2 = posX, posX3 = posX; + bool repositioning = true; + + while (repositioning) { + if (isDropable(posX3, posY) && _screen->getDrawLayer(posX3, posY) < 7 && checkItemCollision(posX3, posY) == -1) { + itemX = posX3; + itemY = posY; + needRepositioning = false; + repositioning = false; + } + + if (isDropable(posX2, posY) && _screen->getDrawLayer(posX2, posY) < 7 && checkItemCollision(posX2, posY) == -1) { + itemX = posX2; + itemY = posY; + needRepositioning = false; + repositioning = false; + } + + if (repositioning) { + posX3 = MAX(posX3 - 2, 16); + posX2 = MIN(posX2 + 2, 304); + + if (posX3 <= 16 && posX2 >= 304) + repositioning = false; + } + } + } + + if (posY == 136) + needRepositioning = false; + else + posY = MIN(posY + 2, 136); + } + + if (itemX == -1 || itemY == -1) + return false; + + if (unk1 == 3) { + _itemList[freeItemSlot].x = itemX; + _itemList[freeItemSlot].y = itemY; + return true; + } else if (unk1 == 2) { + itemDropDown(x, y, itemX, itemY, freeItemSlot, item); + } + + if (!unk1) + removeHandItem(); + + itemDropDown(x, y, itemX, itemY, freeItemSlot, item); + + if (!unk1 && unk2) { + int itemStr = 3; + if (_lang == 1) + itemStr = getItemCommandStringDrop(item); + updateCommandLineEx(item+54, itemStr, 0xD6); + } + + return true; +} + +void KyraEngine_HoF::itemDropDown(int startX, int startY, int dstX, int dstY, int itemSlot, Item item) { + uint8 *itemShape = getShapePtr(item + 64); + + if (startX == dstX && startY == dstY) { + if (_layerFlagTable[_screen->getLayer(dstX, dstY)] && item != 13) { + updateCharFacing(); + snd_playSoundEffect(0x2D); + removeHandItem(); + objectChat(getTableString(0xFF, _cCodeBuffer, 1), 0, 0x83, 0xFF); + } else { + _itemList[itemSlot].x = dstX; + _itemList[itemSlot].y = dstY; + _itemList[itemSlot].id = item; + _itemList[itemSlot].sceneId = _mainCharacter.sceneId; + snd_playSoundEffect(0x0C); + addItemToAnimList(itemSlot); + } + } else { + _screen->hideMouse(); + + if (startY <= dstY) { + int speed = 2; + int curY = startY; + int curX = startX - 8; + + backUpGfxRect24x24(curX, curY-16); + while (curY < dstY) { + restoreGfxRect24x24(curX, curY-16); + + curY = MIN(curY + speed, dstY); + ++speed; + + backUpGfxRect24x24(curX, curY-16); + uint32 endDelay = _system->getMillis() + _tickLength; + + _screen->drawShape(0, itemShape, curX, curY-16, 0, 0); + _screen->updateScreen(); + + delayUntil(endDelay, false, true); + } + + if (dstX != dstY || (dstY - startY > 16)) { + snd_playSoundEffect(0x69); + speed = MAX(speed, 6); + int speedX = ((dstX - startX) << 4) / speed; + int origSpeed = speed; + speed >>= 1; + + if (dstY - startY <= 8) + speed >>= 1; + + speed = -speed; + + curX = startX << 4; + + int x = 0, y = 0; + while (--origSpeed) { + x = (curX >> 4) - 8; + y = curY - 16; + + restoreGfxRect24x24(x, y); + curY = MIN(curY + speed, dstY); + curX += speedX; + ++speed; + + x = (curX >> 4) - 8; + y = curY - 16; + backUpGfxRect24x24(x, y); + + uint16 endDelay = _system->getMillis() + _tickLength; + _screen->drawShape(0, itemShape, x, y, 0, 0); + _screen->updateScreen(); + + delayUntil(endDelay, false, true); + } + + restoreGfxRect24x24(x, y); + } else { + restoreGfxRect24x24(curX, curY-16); + } + } + + if (_layerFlagTable[_screen->getLayer(dstX, dstY)] && item != 13) { + updateCharFacing(); + snd_playSoundEffect(0x2D); + removeHandItem(); + _screen->showMouse(); + objectChat(getTableString(0xFF, _cCodeBuffer, 1), 0, 0x83, 0xFF); + } else { + _itemList[itemSlot].x = dstX; + _itemList[itemSlot].y = dstY; + _itemList[itemSlot].id = item; + _itemList[itemSlot].sceneId = _mainCharacter.sceneId; + snd_playSoundEffect(0x0C); + addItemToAnimList(itemSlot); + _screen->showMouse(); + } + } +} + +void KyraEngine_HoF::exchangeMouseItem(int itemPos) { + deleteItemAnimEntry(itemPos); + + int itemId = _itemList[itemPos].id; + _itemList[itemPos].id = _itemInHand; + _itemInHand = itemId; + + addItemToAnimList(itemPos); + snd_playSoundEffect(0x0B); + setMouseCursor(_itemInHand); + int str2 = 7; + + if (_lang == 1) + str2 = getItemCommandStringPickUp(itemId); + + updateCommandLineEx(itemId + 54, str2, 0xD6); + + runSceneScript6(); +} + +bool KyraEngine_HoF::pickUpItem(int x, int y) { + int itemPos = checkItemCollision(x, y); + + if (itemPos <= -1) + return false; + + if (_itemInHand >= 0) { + exchangeMouseItem(itemPos); + } else { + deleteItemAnimEntry(itemPos); + int itemId = _itemList[itemPos].id; + _itemList[itemPos].id = kItemNone; + snd_playSoundEffect(0x0B); + setMouseCursor(itemId); + int str2 = 7; + + if (_lang == 1) + str2 = getItemCommandStringPickUp(itemId); + + updateCommandLineEx(itemId + 54, str2, 0xD6); + _itemInHand = itemId; + + runSceneScript6(); + } + + return true; +} + +bool KyraEngine_HoF::isDropable(int x, int y) { + if (x < 14 || x > 304 || y < 14 || y > 136) + return false; + + x -= 8; + y -= 1; + + for (int xpos = x; xpos < x + 16; ++xpos) { + if (_screen->getShapeFlag1(xpos, y) == 0) + return false; + } + + return true; +} + +int KyraEngine_HoF::getItemCommandStringDrop(Item item) { + assert(item >= 0 && item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + + static const int dropStringIds[] = { + 0x2D, 0x103, 0x003, 0x106 + }; + assert(stringId < ARRAYSIZE(dropStringIds)); + + return dropStringIds[stringId]; +} + +int KyraEngine_HoF::getItemCommandStringPickUp(Item item) { + assert(item >= 0 && item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + + static const int pickUpStringIds[] = { + 0x02B, 0x102, 0x007, 0x105 + }; + assert(stringId < ARRAYSIZE(pickUpStringIds)); + + return pickUpStringIds[stringId]; +} + +int KyraEngine_HoF::getItemCommandStringInv(Item item) { + assert(item >= 0 && item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + + static const int pickUpStringIds[] = { + 0x02C, 0x104, 0x008, 0x107 + }; + assert(stringId < ARRAYSIZE(pickUpStringIds)); + + return pickUpStringIds[stringId]; +} + +bool KyraEngine_HoF::itemIsFlask(Item item) { + for (int i = 0; _flaskTable[i] != kItemNone; ++i) { + if (_flaskTable[i] == item) + return true; + } + + return false; +} + +void KyraEngine_HoF::setMouseCursor(Item item) { + int shape = 0; + int hotX = 1; + int hotY = 1; + + if (item != kItemNone) { + hotX = 8; + hotY = 15; + shape = item+64; + } + + _screen->setMouseCursor(hotX, hotY, getShapePtr(shape)); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/items_lok.cpp b/engines/kyra/engine/items_lok.cpp new file mode 100644 index 0000000000..5927ba0060 --- /dev/null +++ b/engines/kyra/engine/items_lok.cpp @@ -0,0 +1,960 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_lok.h" +#include "kyra/graphics/animator_lok.h" + +#include "common/system.h" + +namespace Kyra { + +int KyraEngine_LoK::findDuplicateItemShape(int shape) { + static const uint8 dupTable[] = { + 0x48, 0x46, 0x49, 0x47, 0x4A, 0x46, 0x4B, 0x47, + 0x4C, 0x46, 0x4D, 0x47, 0x5B, 0x5A, 0x5C, 0x5A, + 0x5D, 0x5A, 0x5E, 0x5A, 0xFF, 0xFF + }; + + int i = 0; + + while (dupTable[i] != 0xFF) { + if (dupTable[i] == shape) + return dupTable[i + 1]; + i += 2; + } + return -1; +} + +void KyraEngine_LoK::addToNoDropRects(int x, int y, int w, int h) { + for (int rect = 0; rect < ARRAYSIZE(_noDropRects); ++rect) { + if (_noDropRects[rect].top == -1) { + _noDropRects[rect].left = x; + _noDropRects[rect].top = y; + _noDropRects[rect].right = x + w; + _noDropRects[rect].bottom = y + h; + break; + } + } +} + +void KyraEngine_LoK::clearNoDropRects() { + memset(_noDropRects, -1, sizeof(_noDropRects)); +} + +byte KyraEngine_LoK::findFreeItemInScene(int scene) { + assert(scene < _roomTableSize); + Room *room = &_roomTable[scene]; + + for (int i = 0; i < 12; ++i) { + if (room->itemsTable[i] == kItemNone) + return i; + } + + return 0xFF; +} + +byte KyraEngine_LoK::findItemAtPos(int x, int y) { + assert(_currentCharacter->sceneId < _roomTableSize); + const int8 *itemsTable = _roomTable[_currentCharacter->sceneId].itemsTable; + const uint16 *xposOffset = _roomTable[_currentCharacter->sceneId].itemsXPos; + const uint8 *yposOffset = _roomTable[_currentCharacter->sceneId].itemsYPos; + + int highestYPos = -1; + Item returnValue = kItemNone; + + for (int i = 0; i < 12; ++i) { + if (*itemsTable != kItemNone) { + int xpos = *xposOffset - 11; + int xpos2 = *xposOffset + 10; + if (x > xpos && x < xpos2) { + assert(*itemsTable >= 0); + int itemHeight = _itemHtDat[*itemsTable]; + int ypos = *yposOffset + 3; + int ypos2 = ypos - itemHeight - 3; + + if (y > ypos2 && ypos > y) { + if (highestYPos <= ypos) { + returnValue = i; + highestYPos = ypos; + } + } + } + } + + ++xposOffset; + ++yposOffset; + ++itemsTable; + } + + return returnValue; +} + +void KyraEngine_LoK::placeItemInGenericMapScene(int item, int index) { + static const uint16 itemMapSceneMinTable[] = { + 0x0000, 0x0011, 0x006D, 0x0025, 0x00C7, 0x0000 + }; + static const uint16 itemMapSceneMaxTable[] = { + 0x0010, 0x0024, 0x00C6, 0x006C, 0x00F5, 0x0000 + }; + + int minValue = itemMapSceneMinTable[index]; + int maxValue = itemMapSceneMaxTable[index]; + + while (true) { + int room = _rnd.getRandomNumberRng(minValue, maxValue); + assert(room < _roomTableSize); + int nameIndex = _roomTable[room].nameIndex; + bool placeItem = false; + + switch (nameIndex) { + case 0: case 1: case 2: case 3: + case 4: case 5: case 6: case 11: + case 12: case 16: case 17: case 20: + case 22: case 23: case 25: case 26: + case 27: case 31: case 33: case 34: + case 36: case 37: case 58: case 59: + case 60: case 61: case 83: case 84: + case 85: case 104: case 105: case 106: + placeItem = true; + break; + + case 51: + if (room != 46) + placeItem = true; + break; + + default: + break; + } + + if (placeItem) { + Room *roomPtr = &_roomTable[room]; + if (roomPtr->northExit == 0xFFFF && roomPtr->eastExit == 0xFFFF && roomPtr->southExit == 0xFFFF && roomPtr->westExit == 0xFFFF) + placeItem = false; + else if (_currentCharacter->sceneId == room) + placeItem = false; + } + + if (placeItem) { + if (!processItemDrop(room, item, -1, -1, 2, 0)) + continue; + break; + } + } +} + +void KyraEngine_LoK::setHandItem(Item item) { + setMouseItem(item); + _itemInHand = item; +} + +void KyraEngine_LoK::removeHandItem() { + _screen->setMouseCursor(1, 1, _shapes[0]); + _itemInHand = kItemNone; +} + +void KyraEngine_LoK::setMouseItem(Item item) { + if (item == kItemNone) + _screen->setMouseCursor(1, 1, _shapes[6]); + else + _screen->setMouseCursor(8, 15, _shapes[216 + item]); +} + +void KyraEngine_LoK::wipeDownMouseItem(int xpos, int ypos) { + if (_itemInHand == kItemNone) + return; + + xpos -= 8; + ypos -= 15; + _screen->hideMouse(); + backUpItemRect1(xpos, ypos); + int y = ypos; + int height = 16; + + while (height >= 0) { + restoreItemRect1(xpos, ypos); + _screen->setNewShapeHeight(_shapes[216 + _itemInHand], height); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, _shapes[216 + _itemInHand], xpos, y, 0, 0); + _screen->updateScreen(); + y += 2; + height -= 2; + delayUntil(nextTime); + } + restoreItemRect1(xpos, ypos); + _screen->resetShapeHeight(_shapes[216 + _itemInHand]); + removeHandItem(); + _screen->showMouse(); +} + +void KyraEngine_LoK::setupSceneItems() { + uint16 sceneId = _currentCharacter->sceneId; + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + for (int i = 0; i < 12; ++i) { + uint8 item = currentRoom->itemsTable[i]; + if (item == 0xFF || !currentRoom->needInit[i]) + continue; + + int xpos = 0; + int ypos = 0; + + if (currentRoom->itemsXPos[i] == 0xFFFF) { + xpos = currentRoom->itemsXPos[i] = _rnd.getRandomNumberRng(24, 296); + ypos = currentRoom->itemsYPos[i] = _rnd.getRandomNumberRng(_northExitHeight & 0xFF, 130); + } else { + xpos = currentRoom->itemsXPos[i]; + ypos = currentRoom->itemsYPos[i]; + } + + _lastProcessedItem = i; + + int stop = 0; + while (!stop) { + stop = processItemDrop(sceneId, item, xpos, ypos, 3, 0); + if (!stop) { + xpos = currentRoom->itemsXPos[i] = _rnd.getRandomNumberRng(24, 296); + ypos = currentRoom->itemsYPos[i] = _rnd.getRandomNumberRng(_northExitHeight & 0xFF, 130); + if (countItemsInScene(sceneId) >= 12) + break; + } else { + currentRoom->needInit[i] = 0; + } + } + } +} + +int KyraEngine_LoK::countItemsInScene(uint16 sceneId) { + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + + int items = 0; + + for (int i = 0; i < 12; ++i) { + if (currentRoom->itemsTable[i] != kItemNone) + ++items; + } + + return items; +} + +int KyraEngine_LoK::processItemDrop(uint16 sceneId, uint8 item, int x, int y, int unk1, int unk2) { + int freeItem = -1; + uint8 itemIndex = findItemAtPos(x, y); + if (unk1) + itemIndex = 0xFF; + + if (itemIndex != 0xFF) { + exchangeItemWithMouseItem(sceneId, itemIndex); + return 0; + } + + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + + if (unk1 != 3) { + for (int i = 0; i < 12; ++i) { + if (currentRoom->itemsTable[i] == kItemNone) { + freeItem = i; + break; + } + } + } else { + freeItem = _lastProcessedItem; + } + + if (freeItem == -1) + return 0; + + if (sceneId != _currentCharacter->sceneId) { + addItemToRoom(sceneId, item, freeItem, x, y); + return 1; + } + + int itemHeight = _itemHtDat[item]; + _lastProcessedItemHeight = itemHeight; + + if (x == -1) + x = _rnd.getRandomNumberRng(16, 304); + + if (y == -1) + y = _rnd.getRandomNumberRng(_northExitHeight & 0xFF, 135); + + int xpos = x; + int ypos = y; + int destY = -1; + int destX = -1; + int running = 1; + + while (running) { + if ((_northExitHeight & 0xFF) <= ypos) { + bool running2 = true; + + if (_screen->getDrawLayer(xpos, ypos) > 1) { + if (((_northExitHeight >> 8) & 0xFF) != ypos) + running2 = false; + } + + if (_screen->getDrawLayer2(xpos, ypos, itemHeight) > 1) { + if (((_northExitHeight >> 8) & 0xFF) != ypos) + running2 = false; + } + + if (!isDropable(xpos, ypos)) { + if (((_northExitHeight >> 8) & 0xFF) != ypos) + running2 = false; + } + + int xpos2 = xpos; + int xpos3 = xpos; + + while (running2) { + if (isDropable(xpos2, ypos)) { + if (_screen->getDrawLayer2(xpos2, ypos, itemHeight) < 7) { + if (findItemAtPos(xpos2, ypos) == 0xFF) { + destX = xpos2; + destY = ypos; + running = 0; + running2 = false; + } + } + } + + if (isDropable(xpos3, ypos)) { + if (_screen->getDrawLayer2(xpos3, ypos, itemHeight) < 7) { + if (findItemAtPos(xpos3, ypos) == 0xFF) { + destX = xpos3; + destY = ypos; + running = 0; + running2 = false; + } + } + } + + if (!running2) + continue; + + xpos2 -= 2; + if (xpos2 < 16) + xpos2 = 16; + + xpos3 += 2; + if (xpos3 > 304) + xpos3 = 304; + + if (xpos2 > 16) + continue; + if (xpos3 < 304) + continue; + running2 = false; + } + } + + if (((_northExitHeight >> 8) & 0xFF) == ypos) { + running = 0; + destY -= _rnd.getRandomNumberRng(0, 3); + + if ((_northExitHeight & 0xFF) < destY) + continue; + + destY = (_northExitHeight & 0xFF) + 1; + continue; + } + ypos += 2; + if (((_northExitHeight >> 8) & 0xFF) >= ypos) + continue; + ypos = (_northExitHeight >> 8) & 0xFF; + } + + if (destX == -1 || destY == -1) + return 0; + + if (unk1 == 3) { + currentRoom->itemsXPos[freeItem] = destX; + currentRoom->itemsYPos[freeItem] = destY; + return 1; + } + + if (unk1 == 2) + itemSpecialFX(x, y, item); + + if (unk1 == 0) + removeHandItem(); + + itemDropDown(x, y, destX, destY, freeItem, item); + + if (unk1 == 0 && unk2 != 0) { + assert(_itemList && _droppedList); + updateSentenceCommand(_itemList[getItemListIndex(item)], _droppedList[0], 179); + } + + return 1; +} + +void KyraEngine_LoK::exchangeItemWithMouseItem(uint16 sceneId, int itemIndex) { + _animator->animRemoveGameItem(itemIndex); + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + + int item = currentRoom->itemsTable[itemIndex]; + currentRoom->itemsTable[itemIndex] = _itemInHand; + _itemInHand = item; + _animator->animAddGameItem(itemIndex, sceneId); + snd_playSoundEffect(53); + + setMouseItem(_itemInHand); + assert(_itemList && _takenList); + if (_flags.platform == Common::kPlatformAmiga) + updateSentenceCommand(_itemList[getItemListIndex(_itemInHand)], _takenList[0], 179); + else + updateSentenceCommand(_itemList[getItemListIndex(_itemInHand)], _takenList[1], 179); + clickEventHandler2(); +} + +void KyraEngine_LoK::addItemToRoom(uint16 sceneId, uint8 item, int itemIndex, int x, int y) { + assert(sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[sceneId]; + currentRoom->itemsTable[itemIndex] = item; + currentRoom->itemsXPos[itemIndex] = x; + currentRoom->itemsYPos[itemIndex] = y; + currentRoom->needInit[itemIndex] = 1; +} + +int KyraEngine_LoK::checkNoDropRects(int x, int y) { + if (_lastProcessedItemHeight < 1 || _lastProcessedItemHeight > 16) + _lastProcessedItemHeight = 16; + if (_noDropRects[0].left == -1) + return 0; + + for (int i = 0; i < ARRAYSIZE(_noDropRects); ++i) { + if (_noDropRects[i].left == -1) + break; + + int xpos = _noDropRects[i].left; + int ypos = _noDropRects[i].top; + int xpos2 = _noDropRects[i].right; + int ypos2 = _noDropRects[i].bottom; + + if (xpos > x + 16) + continue; + if (xpos2 <= x) + continue; + if (y < ypos) + continue; + if (ypos2 <= y - _lastProcessedItemHeight) + continue; + return 1; + } + + return 0; +} + +int KyraEngine_LoK::isDropable(int x, int y) { + x -= 8; + y -= 1; + + if (checkNoDropRects(x, y)) + return 0; + + for (int xpos = x; xpos < x + 16; ++xpos) { + if (_screen->getShapeFlag1(xpos, y) == 0) + return 0; + } + return 1; +} + +void KyraEngine_LoK::itemDropDown(int x, int y, int destX, int destY, byte freeItem, int item) { + assert(_currentCharacter->sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[_currentCharacter->sceneId]; + if (x == destX && y == destY) { + currentRoom->itemsXPos[freeItem] = destX; + currentRoom->itemsYPos[freeItem] = destY; + currentRoom->itemsTable[freeItem] = item; + snd_playSoundEffect(0x32); + _animator->animAddGameItem(freeItem, _currentCharacter->sceneId); + return; + } + _screen->hideMouse(); + if (y <= destY) { + int tempY = y; + int addY = 2; + int drawX = x - 8; + int drawY = 0; + + backUpItemRect0(drawX, y - 16); + + while (tempY < destY) { + restoreItemRect0(drawX, tempY - 16); + tempY += addY; + if (tempY > destY) + tempY = destY; + ++addY; + drawY = tempY - 16; + backUpItemRect0(drawX, drawY); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, _shapes[216 + item], drawX, drawY, 0, 0); + _screen->updateScreen(); + delayUntil(nextTime); + } + + bool skip = false; + if (x == destX) { + if (destY - y <= 16) + skip = true; + } + + if (!skip) { + snd_playSoundEffect(0x47); + if (addY < 6) + addY = 6; + + int xDiff = (destX - x) << 4; + xDiff /= addY; + int startAddY = addY; + addY >>= 1; + if (destY - y <= 8) + addY >>= 1; + addY = -addY; + int unkX = x << 4; + while (--startAddY) { + drawX = (unkX >> 4) - 8; + drawY = tempY - 16; + restoreItemRect0(drawX, drawY); + tempY += addY; + unkX += xDiff; + if (tempY > destY) + tempY = destY; + ++addY; + drawX = (unkX >> 4) - 8; + drawY = tempY - 16; + backUpItemRect0(drawX, drawY); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, _shapes[216 + item], drawX, drawY, 0, 0); + _screen->updateScreen(); + delayUntil(nextTime); + } + restoreItemRect0(drawX, drawY); + } else { + restoreItemRect0(drawX, tempY - 16); + } + } + currentRoom->itemsXPos[freeItem] = destX; + currentRoom->itemsYPos[freeItem] = destY; + currentRoom->itemsTable[freeItem] = item; + snd_playSoundEffect(0x32); + _animator->animAddGameItem(freeItem, _currentCharacter->sceneId); + _screen->showMouse(); +} + +void KyraEngine_LoK::dropItem(int unk1, int item, int x, int y, int unk2) { + if (processItemDrop(_currentCharacter->sceneId, item, x, y, unk1, unk2)) + return; + snd_playSoundEffect(54); + + // Old floppy versions don't print warning messages and don't have the necessary string resources. + // These versions will only play the warning sound effect. + if (_flags.isOldFloppy && !_noDropList) + return; + + assert(_noDropList); + + if (12 == countItemsInScene(_currentCharacter->sceneId)) + drawSentenceCommand(_noDropList[0], 6); + else + drawSentenceCommand(_noDropList[1], 6); +} + +void KyraEngine_LoK::itemSpecialFX(int x, int y, int item) { + if (item == 41) + itemSpecialFX1(x, y, item); + else + itemSpecialFX2(x, y, item); +} + +void KyraEngine_LoK::itemSpecialFX1(int x, int y, int item) { + uint8 *shape = _shapes[216 + item]; + x -= 8; + int startY = y; + y -= 15; + _screen->hideMouse(); + backUpItemRect0(x, y); + for (int i = 1; i <= 16; ++i) { + _screen->setNewShapeHeight(shape, i); + --startY; + restoreItemRect0(x, y); + uint32 nextTime = _system->getMillis() + 1 * _tickLength; + _screen->drawShape(0, shape, x, startY, 0, 0); + _screen->updateScreen(); + delayUntil(nextTime); + } + restoreItemRect0(x, y); + _screen->showMouse(); +} + +void KyraEngine_LoK::itemSpecialFX2(int x, int y, int item) { + x -= 8; + y -= 15; + int yAdd = (int8)(((16 - _itemHtDat[item]) >> 1) & 0xFF); + backUpItemRect0(x, y); + if (item >= 80 && item <= 89) + snd_playSoundEffect(55); + + for (int i = 201; i <= 205; ++i) { + restoreItemRect0(x, y); + uint32 nextTime = _system->getMillis() + 3 * _tickLength; + _screen->drawShape(0, _shapes[i], x, y + yAdd, 0, 0); + _screen->updateScreen(); + delayUntil(nextTime); + } + + for (int i = 204; i >= 201; --i) { + restoreItemRect0(x, y); + uint32 nextTime = _system->getMillis() + 3 * _tickLength; + _screen->drawShape(0, _shapes[216 + item], x, y, 0, 0); + _screen->drawShape(0, _shapes[i], x, y + yAdd, 0, 0); + _screen->updateScreen(); + delayUntil(nextTime); + } + restoreItemRect0(x, y); +} + +void KyraEngine_LoK::magicOutMouseItem(int animIndex, int itemPos) { + int videoPageBackUp = _screen->_curPage; + _screen->_curPage = 0; + int x = 0, y = 0; + + if (itemPos == kItemNone) { + Common::Point mouse = getMousePos(); + x = mouse.x - 12; + y = mouse.y - 18; + } else { + x = _itemPosX[itemPos] - 4; + y = _itemPosY[itemPos] - 3; + } + + if (_itemInHand == kItemNone && itemPos == -1) + return; + + int tableIndex = 0, loopStart = 0, maxLoops = 0; + if (animIndex == 0) { + tableIndex = _rnd.getRandomNumberRng(0, 5); + loopStart = 35; + maxLoops = 9; + } else if (animIndex == 1) { + tableIndex = _rnd.getRandomNumberRng(0, 11); + loopStart = 115; + maxLoops = 8; + } else if (animIndex == 2) { + tableIndex = 0; + loopStart = 124; + maxLoops = 4; + } else { + tableIndex = -1; + } + + if (animIndex == 2) + snd_playSoundEffect(0x5E); + else + snd_playSoundEffect(0x37); + _screen->hideMouse(); + backUpItemRect1(x, y); + + for (int shape = _magicMouseItemStartFrame[animIndex]; shape <= _magicMouseItemEndFrame[animIndex]; ++shape) { + restoreItemRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + _screen->drawShape(0, _shapes[216 + _itemInHand], x + 4, y + 3, 0, 0); + if (tableIndex == -1) + _screen->drawShape(0, _shapes[shape], x, y, 0, 0); + else + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + _screen->updateScreen(); + delayUntil(nextTime); + } + + if (itemPos != -1) { + restoreItemRect1(x, y); + _screen->fillRect(_itemPosX[itemPos], _itemPosY[itemPos], _itemPosX[itemPos] + 15, _itemPosY[itemPos] + 15, _flags.platform == Common::kPlatformAmiga ? 19 : 12, 0); + backUpItemRect1(x, y); + } + + for (int shape = _magicMouseItemStartFrame2[animIndex]; shape <= _magicMouseItemEndFrame2[animIndex]; ++shape) { + restoreItemRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + _screen->drawShape(0, _shapes[216 + _itemInHand], x + 4, y + 3, 0, 0); + if (tableIndex == -1) + _screen->drawShape(0, _shapes[shape], x, y, 0, 0); + else + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + _screen->updateScreen(); + delayUntil(nextTime); + } + restoreItemRect1(x, y); + + if (itemPos == -1) { + _screen->setMouseCursor(1, 1, _shapes[0]); + _itemInHand = kItemNone; + } else { + _characterList[0].inventoryItems[itemPos] = kItemNone; + _screen->fillRect(_itemPosX[itemPos], _itemPosY[itemPos], _itemPosX[itemPos] + 15, _itemPosY[itemPos] + 15, _flags.platform == Common::kPlatformAmiga ? 19 : 12, 0); + } + + _screen->showMouse(); + _screen->_curPage = videoPageBackUp; +} + +void KyraEngine_LoK::magicInMouseItem(int animIndex, int item, int itemPos) { + int videoPageBackUp = _screen->_curPage; + _screen->_curPage = 0; + int x = 0, y = 0; + if (itemPos == -1) { + Common::Point mouse = getMousePos(); + x = mouse.x - 12; + y = mouse.y - 18; + } else { + x = _itemPosX[itemPos] - 4; + y = _itemPosX[itemPos] - 3; + } + if (item < 0) + return; + + int tableIndex = -1, loopStart = 0, maxLoops = 0; + if (animIndex == 0) { + tableIndex = _rnd.getRandomNumberRng(0, 5); + loopStart = 35; + maxLoops = 9; + } else if (animIndex == 1) { + tableIndex = _rnd.getRandomNumberRng(0, 11); + loopStart = 115; + maxLoops = 8; + } else if (animIndex == 2) { + tableIndex = 0; + loopStart = 124; + maxLoops = 4; + } + + _screen->hideMouse(); + backUpItemRect1(x, y); + if (animIndex == 2) + snd_playSoundEffect(0x5E); + else + snd_playSoundEffect(0x37); + + for (int shape = _magicMouseItemStartFrame[animIndex]; shape <= _magicMouseItemEndFrame[animIndex]; ++shape) { + restoreItemRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + if (tableIndex == -1) + _screen->drawShape(0, _shapes[shape], x, y, 0, 0); + else + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + _screen->updateScreen(); + delayUntil(nextTime); + } + + for (int shape = _magicMouseItemStartFrame2[animIndex]; shape <= _magicMouseItemEndFrame2[animIndex]; ++shape) { + restoreItemRect1(x, y); + uint32 nextTime = _system->getMillis() + 4 * _tickLength; + if (tableIndex == -1) + _screen->drawShape(0, _shapes[shape], x, y, 0, 0); + else + specialMouseItemFX(shape, x, y, animIndex, tableIndex, loopStart, maxLoops); + _screen->updateScreen(); + delayUntil(nextTime); + } + restoreItemRect1(x, y); + if (itemPos == -1) { + _screen->setMouseCursor(8, 15, _shapes[216 + item]); + _itemInHand = item; + } else { + _characterList[0].inventoryItems[itemPos] = item; + _screen->drawShape(0, _shapes[216 + item], _itemPosX[itemPos], _itemPosY[itemPos], 0, 0); + } + _screen->showMouse(); + _screen->_curPage = videoPageBackUp; +} + +void KyraEngine_LoK::specialMouseItemFX(int shape, int x, int y, int animIndex, int tableIndex, int loopStart, int maxLoops) { + static const uint8 table1[] = { + 0x23, 0x45, 0x55, 0x72, 0x84, 0xCF, 0x00, 0x00 + }; + static const uint8 table2[] = { + 0x73, 0xB5, 0x80, 0x21, 0x13, 0x39, 0x45, 0x55, 0x62, 0xB4, 0xCF, 0xD8 + }; + static const uint8 table3[] = { + 0x7C, 0xD0, 0x74, 0x84, 0x87, 0x00, 0x00, 0x00 + }; + int tableValue = 0; + if (animIndex == 0) + tableValue = table1[tableIndex]; + else if (animIndex == 1) + tableValue = table2[tableIndex]; + else if (animIndex == 2) + tableValue = table3[tableIndex]; + else + return; + processSpecialMouseItemFX(shape, x, y, tableValue, loopStart, maxLoops); +} + +void KyraEngine_LoK::processSpecialMouseItemFX(int shape, int x, int y, int tableValue, int loopStart, int maxLoops) { + uint8 shapeColorTable[16]; + uint8 *shapePtr = _shapes[shape] + 10; + if (_flags.useAltShapeHeader) + shapePtr += 2; + + for (int i = 0; i < 16; ++i) + shapeColorTable[i] = shapePtr[i]; + + for (int i = loopStart; i < loopStart + maxLoops; ++i) { + for (int i2 = 0; i2 < 16; ++i2) { + if (shapePtr[i2] == i) + shapeColorTable[i2] = (i + tableValue) - loopStart; + } + } + _screen->drawShape(0, _shapes[shape], x, y, 0, 0x8000, shapeColorTable); +} + +void KyraEngine_LoK::updatePlayerItemsForScene() { + if (_itemInHand >= 29 && _itemInHand < 33) { + ++_itemInHand; + if (_itemInHand > 33) + _itemInHand = 33; + _screen->setMouseCursor(8, 15, _shapes[216 + _itemInHand]); + } + + bool redraw = false; + for (int i = 0; i < 10; ++i) { + uint8 item = _currentCharacter->inventoryItems[i]; + if (item >= 29 && item < 33) { + ++item; + _currentCharacter->inventoryItems[i] = item; + redraw = true; + } + } + + if (redraw) { + redrawInventory(0); + } + + if (_itemInHand == 33) + magicOutMouseItem(2, -1); + + _screen->hideMouse(); + for (int i = 0; i < 10; ++i) { + uint8 item = _currentCharacter->inventoryItems[i]; + if (item == 33) + magicOutMouseItem(2, i); + } + _screen->showMouse(); +} + +void KyraEngine_LoK::redrawInventory(int page) { + int videoPageBackUp = _screen->_curPage; + _screen->_curPage = page; + for (int i = 0; i < 10; ++i) { + _screen->fillRect(_itemPosX[i], _itemPosY[i], _itemPosX[i] + 15, _itemPosY[i] + 15, _flags.platform == Common::kPlatformAmiga ? 19 : 12, page); + + if (_currentCharacter->inventoryItems[i] != kItemNone) { + uint8 item = _currentCharacter->inventoryItems[i]; + _screen->drawShape(page, _shapes[216 + item], _itemPosX[i], _itemPosY[i], 0, 0); + } + } + _screen->_curPage = videoPageBackUp; + _screen->updateScreen(); +} + +void KyraEngine_LoK::backUpItemRect0(int xpos, int ypos) { + _screen->rectClip(xpos, ypos, 3 << 3, 24); + _screen->copyRegionToBuffer(_screen->_curPage, xpos, ypos, 3 << 3, 24, _itemBkgBackUp[0]); +} + +void KyraEngine_LoK::restoreItemRect0(int xpos, int ypos) { + _screen->rectClip(xpos, ypos, 3 << 3, 24); + _screen->copyBlockToPage(_screen->_curPage, xpos, ypos, 3 << 3, 24, _itemBkgBackUp[0]); +} + +void KyraEngine_LoK::backUpItemRect1(int xpos, int ypos) { + _screen->rectClip(xpos, ypos, 4 << 3, 32); + _screen->copyRegionToBuffer(_screen->_curPage, xpos, ypos, 4 << 3, 32, _itemBkgBackUp[1]); +} + +void KyraEngine_LoK::restoreItemRect1(int xpos, int ypos) { + _screen->rectClip(xpos, ypos, 4 << 3, 32); + _screen->copyBlockToPage(_screen->_curPage, xpos, ypos, 4 << 3, 32, _itemBkgBackUp[1]); +} + +int KyraEngine_LoK::getItemListIndex(Item item) { + if (_flags.platform != Common::kPlatformAmiga) + return item; + + // "Unknown item" is at 81. + if (item == kItemNone) + return 81; + // The first item names are mapped directly + else if (item <= 28) + return item; + // There's only one string for "Fireberries" + else if (item >= 29 && item <= 33) + return 29; + // Correct offsets + else if (item >= 34 && item <= 59) + return item - 4; + // There's only one string for "Red Potion" + else if (item >= 60 && item <= 61) + return 56; + // There's only one string for "Blue Potion" + else if (item >= 62 && item <= 63) + return 57; + // There's only one string for "Yellow Potion" + else if (item >= 64 && item <= 65) + return 58; + // Correct offsets + else if (item >= 66 && item <= 69) + return item - 7; + // There's only one string for "Fresh Water" + else if (item >= 70 && item <= 71) + return 63; + // There's only one string for "Salt Water" + else if (item >= 72 && item <= 73) + return 64; + // There's only one string for "Mineral Water" + else if (item >= 74 && item <= 75) + return 65; + // There's only one string for "Magical Water" + else if (item >= 76 && item <= 77) + return 66; + // There's only one string for "Empty Flask" + else if (item >= 78 && item <= 79) + return 67; + // There's only one string for "Scroll" + else if (item >= 80 && item <= 89) + return 68; + // There's only one string for "Parchment scrap" + else if (item >= 90 && item <= 94) + return 69; + // Correct offsets + else if (item >= 95) + return item - 25; + + // This should never happen, but still GCC warns about it. + return 81; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/items_lol.cpp b/engines/kyra/engine/items_lol.cpp new file mode 100644 index 0000000000..446650d6e1 --- /dev/null +++ b/engines/kyra/engine/items_lol.cpp @@ -0,0 +1,594 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/engine/lol.h" +#include "kyra/graphics/screen_lol.h" + +namespace Kyra { + +LoLObject *LoLEngine::findObject(uint16 index) { + if (index & 0x8000) + return &_monsters[index & 0x7FFF]; + else + return &_itemsInPlay[index]; +} + +int LoLEngine::calcObjectPosition(LoLObject *i, uint16 direction) { + int x = i->x; + int y = i->y; + + calcSpriteRelPosition(_partyPosX, _partyPosY, x, y, direction); + + if (y < 0) + y = 0; + + int res = (i->flyingHeight << 12); + res |= (4095 - y); + + return res; +} + +void LoLEngine::removeAssignedObjectFromBlock(LevelBlockProperty *l, uint16 id) { + uint16 *blockItemIndex = &l->assignedObjects; + LoLObject *i = 0; + + while (*blockItemIndex) { + if (*blockItemIndex == id) { + i = findObject(id); + *blockItemIndex = i->nextAssignedObject; + i->nextAssignedObject = 0; + return; + } + + i = findObject(*blockItemIndex); + blockItemIndex = &i->nextAssignedObject; + } +} + +void LoLEngine::removeDrawObjectFromBlock(LevelBlockProperty *l, uint16 id) { + uint16 *blockItemIndex = &l->drawObjects; + LoLObject *i = 0; + + while (*blockItemIndex) { + if (*blockItemIndex == id) { + i = findObject(id); + *blockItemIndex = i->nextDrawObject; + i->nextDrawObject = 0; + return; + } + + i = findObject(*blockItemIndex); + blockItemIndex = &i->nextDrawObject; + } +} + +void LoLEngine::assignObjectToBlock(uint16 *assignedBlockObjects, uint16 id) { + LoLObject *t = findObject(id); + t->nextAssignedObject = *assignedBlockObjects; + *assignedBlockObjects = id; +} + +void LoLEngine::giveCredits(int credits, int redraw) { + if (redraw) + snd_playSoundEffect(101, -1); + + int t = credits / 30; + if (!t) + t = 1; + + int cnt = 0; + + while (credits) { + if (t > credits) + t = credits; + + if (_credits < 60 && t > 0) { + cnt = 0; + + do { + if (_credits < 60) { + int d = _stashSetupData[_credits % 12] - _credits / 12; + if (d < 0) + d += 5; + _moneyColumnHeight[d]++; + } + _credits++; + } while (++cnt < t); + } else if (_credits >= 60) { + _credits += t; + } + + if (redraw) { + gui_drawMoneyBox(6); + if (credits) + delay(_tickLength, 1); + } + credits -= t; + } +} + +void LoLEngine::takeCredits(int credits, int redraw) { + if (redraw) + snd_playSoundEffect(101, -1); + + if (credits > _credits) + credits = _credits; + + int t = credits / 30; + if (!t) + t = 1; + + int cnt = 0; + + while (credits && _credits > 0) { + if (t > credits) + t = credits; + + if (_credits - t < 60 && t > 0) { + cnt = 0; + + do { + if (--_credits < 60) { + int d = _stashSetupData[_credits % 12] - _credits / 12; + if (d < 0) + d += 5; + _moneyColumnHeight[d]--; + } + } while (++cnt < t); + } else if (_credits - t >= 60) { + _credits -= t; + } + + if (redraw) { + gui_drawMoneyBox(6); + if (credits) + delay(_tickLength, 1); + } + credits -= t; + } +} + +Item LoLEngine::makeItem(int itemType, int curFrame, int flags) { + int cnt = 0; + int r = 0; + Item i = 1; + + for (; i < 400; i++) { + if (_itemsInPlay[i].shpCurFrame_flg & 0x8000) { + cnt = 0; + break; + } + + if (_itemsInPlay[i].level < 1 || _itemsInPlay[i].level > 29 || _itemsInPlay[i].level == _currentLevel) + continue; + + int diff = ABS(_currentLevel - _itemsInPlay[i].level); + + if (diff <= cnt) + continue; + + bool t = false; + for (Item ii = i; ii && !t; ii = _itemsInPlay[ii].nextAssignedObject) + t = isItemMoveable(ii); + + if (t) { + cnt = diff; + r = i; + } + } + + Item slot = i; + if (cnt) { + slot = 0; + if (isItemMoveable(r)) { + if (_itemsInPlay[r].nextAssignedObject) + _itemsInPlay[_itemsInPlay[r].nextAssignedObject].level = _itemsInPlay[r].level; + deleteItem(r); + slot = r; + } else { + for (uint16 ii = _itemsInPlay[r].nextAssignedObject; ii; ii = _itemsInPlay[ii].nextAssignedObject) { + if (!isItemMoveable(ii)) + continue; + _itemsInPlay[r].nextAssignedObject = _itemsInPlay[ii].nextAssignedObject; + deleteItem(ii); + slot = ii; + break; + } + } + } + + memset(&_itemsInPlay[slot], 0, sizeof(LoLItem)); + + _itemsInPlay[slot].itemPropertyIndex = itemType; + _itemsInPlay[slot].shpCurFrame_flg = (curFrame & 0x1FFF) | flags; + _itemsInPlay[slot].level = -1; + + return slot; +} + +void LoLEngine::placeMoveLevelItem(Item itemIndex, int level, int block, int xOffs, int yOffs, int flyingHeight) { + calcCoordinates(_itemsInPlay[itemIndex].x, _itemsInPlay[itemIndex].y, block, xOffs, yOffs); + + if (_itemsInPlay[itemIndex].block) + removeLevelItem(itemIndex, _itemsInPlay[itemIndex].block); + + if (_currentLevel == level) { + setItemPosition(itemIndex, _itemsInPlay[itemIndex].x, _itemsInPlay[itemIndex].y, flyingHeight, 1); + } else { + _itemsInPlay[itemIndex].level = level; + _itemsInPlay[itemIndex].block = block; + _itemsInPlay[itemIndex].flyingHeight = flyingHeight; + _itemsInPlay[itemIndex].shpCurFrame_flg |= 0x4000; + } +} + +bool LoLEngine::addItemToInventory(Item itemIndex) { + int pos = 0; + int i = 0; + + for (; i < 48; i++) { + pos = _inventoryCurItem + i; + if (pos > 47) + pos -= 48; + + if (!_inventory[pos]) + break; + } + + if (i == 48) + return false; + + while ((_inventoryCurItem > pos) || ((_inventoryCurItem + 9) <= pos)) { + if (++_inventoryCurItem > 47) + _inventoryCurItem -= 48; + gui_drawInventory(); + } + + assert(pos >= 0 && pos < 48); + _inventory[pos] = itemIndex; + gui_drawInventory(); + + return true; +} + +bool LoLEngine::isItemMoveable(Item itemIndex) { + if (!(_itemsInPlay[itemIndex].shpCurFrame_flg & 0x4000)) + return false; + + if (_itemProperties[_itemsInPlay[itemIndex].itemPropertyIndex].flags & 4) + return false; + + return true; + +} + +void LoLEngine::deleteItem(Item itemIndex) { + memset(&_itemsInPlay[itemIndex], 0, sizeof(LoLItem)); + _itemsInPlay[itemIndex].shpCurFrame_flg |= 0x8000; +} + +void LoLEngine::runItemScript(int charNum, Item item, int flags, int next, int reg4) { + EMCState scriptState; + memset(&scriptState, 0, sizeof(EMCState)); + + uint8 func = item ? _itemProperties[_itemsInPlay[item].itemPropertyIndex].itemScriptFunc : 3; + if (func == 0xFF) + return; + + _emc->init(&scriptState, &_itemScript); + _emc->start(&scriptState, func); + + scriptState.regs[0] = flags; + scriptState.regs[1] = charNum; + scriptState.regs[2] = item; + scriptState.regs[3] = next; + scriptState.regs[4] = reg4; + + if (_emc->isValid(&scriptState)) { + if (*(scriptState.ip - 1) & flags) { + while (_emc->isValid(&scriptState)) + _emc->run(&scriptState); + } + } +} + +void LoLEngine::setHandItem(Item itemIndex) { + if (itemIndex && _itemProperties[_itemsInPlay[itemIndex].itemPropertyIndex].flags & 0x80) { + runItemScript(-1, itemIndex, 0x400, 0, 0); + if (_itemsInPlay[itemIndex].shpCurFrame_flg & 0x8000) + itemIndex = 0; + } + + int mouseOffs = 0; + + if (itemIndex && !(_flagsTable[31] & 0x02)) { + mouseOffs = 10; + if (!_currentControlMode || textEnabled()) + _txt->printMessage(0, getLangString(0x403E), getLangString(_itemProperties[_itemsInPlay[itemIndex].itemPropertyIndex].nameStringId)); + } + + _itemInHand = itemIndex; + _screen->setMouseCursor(mouseOffs, mouseOffs, getItemIconShapePtr(itemIndex)); +} + +bool LoLEngine::itemEquipped(int charNum, uint16 itemType) { + if (charNum < 0 || charNum > 3) + return false; + + if (!(_characters[charNum].flags & 1)) + return false; + + for (int i = 0; i < 11; i++) { + if (!_characters[charNum].items[i]) + continue; + + if (_itemsInPlay[_characters[charNum].items[i]].itemPropertyIndex == itemType) + return true; + } + + return false; +} + +void LoLEngine::setItemPosition(Item item, uint16 x, uint16 y, int flyingHeight, int moveable) { + if (!flyingHeight) { + x = (x & 0xFFC0) | 0x40; + y = (y & 0xFFC0) | 0x40; + } + + uint16 block = calcBlockIndex(x, y); + _itemsInPlay[item].x = x; + _itemsInPlay[item].y = y; + _itemsInPlay[item].block = block; + _itemsInPlay[item].flyingHeight = flyingHeight; + + if (moveable) + _itemsInPlay[item].shpCurFrame_flg |= 0x4000; + else + _itemsInPlay[item].shpCurFrame_flg &= 0xBFFF; + + + assignItemToBlock(&_levelBlockProperties[block].assignedObjects, item); + reassignDrawObjects(_currentDirection, item, &_levelBlockProperties[block], false); + + if (moveable) + runLevelScriptCustom(block, 0x80, -1, item, 0, 0); + + checkSceneUpdateNeed(block); +} + +void LoLEngine::removeLevelItem(Item item, int block) { + removeAssignedObjectFromBlock(&_levelBlockProperties[block], item); + removeDrawObjectFromBlock(&_levelBlockProperties[block], item); + runLevelScriptCustom(block, 0x100, -1, item, 0, 0); + _itemsInPlay[item].block = 0; + _itemsInPlay[item].level = 0; +} + +bool LoLEngine::launchObject(int objectType, Item item, int startX, int startY, int flyingHeight, int direction, int, int attackerId, int c) { + int sp = checkDrawObjectSpace(_partyPosX, _partyPosY, startX, startY); + FlyingObject *t = _flyingObjects; + int slot = -1; + int i = 0; + + for (; i < 8; i++) { + if (!t->enable) { + sp = -1; + break; + } + + int csp = checkDrawObjectSpace(_partyPosX, _partyPosY, t->x, t->y); + if (csp > sp) { + sp = csp; + slot = i; + } + t++; + } + + if (sp != -1 && slot != -1) { + i = slot; + + t = &_flyingObjects[i]; + endObjectFlight(t, startX, startY, 8); + } + + if (i == 8) + return false; + + t->enable = 1; + t->objectType = objectType; + t->item = item; + t->x = startX; + t->y = startY; + t->flyingHeight = flyingHeight; + t->direction = direction; + t->distance = 255; + t->attackerId = attackerId; + t->flags = 7; + t->wallFlags = 2; + t->c = c; + + if (attackerId != -1) { + if (attackerId & 0x8000) { + t->flags &= 0xFD; + } else { + t->flags &= 0xFB; + increaseExperience(attackerId, 1, 2); + } + } + + updateObjectFlightPosition(t); + + return true; +} + +void LoLEngine::endObjectFlight(FlyingObject *t, int x, int y, int collisionType) { + int cx = x; + int cy = y; + uint16 block = calcBlockIndex(t->x, t->y); + removeAssignedObjectFromBlock(&_levelBlockProperties[block], t->item); + removeDrawObjectFromBlock(&_levelBlockProperties[block], t->item); + + if (collisionType == 1) { + cx = t->x; + cy = t->y; + } + + if (t->objectType == 0 || t->objectType == 1) { + objectFlightProcessHits(t, cx, cy, collisionType); + t->x = (cx & 0xFFC0) | 0x40; + t->y = (cy & 0xFFC0) | 0x40; + t->flyingHeight = 0; + updateObjectFlightPosition(t); + } + + t->enable = 0; +} + +void LoLEngine::processObjectFlight(FlyingObject *t, int x, int y) { + int bl = calcBlockIndex(t->x, t->y); + LevelBlockProperty *l = &_levelBlockProperties[bl]; + removeAssignedObjectFromBlock(l, t->item); + removeDrawObjectFromBlock(l, t->item); + t->x = x; + t->y = y; + updateObjectFlightPosition(t); + checkSceneUpdateNeed(bl); +} + +void LoLEngine::updateObjectFlightPosition(FlyingObject *t) { + if (t->objectType == 0) { + setItemPosition(t->item, t->x, t->y, t->flyingHeight, (t->flyingHeight == 0) ? 1 : 0); + } else if (t->objectType == 1) { + if (t->flyingHeight == 0) { + deleteItem(t->item); + checkSceneUpdateNeed(calcBlockIndex(t->x, t->y)); + } else { + setItemPosition(t->item, t->x, t->y, t->flyingHeight, 0); + } + } +} + +void LoLEngine::objectFlightProcessHits(FlyingObject *t, int x, int y, int collisionType) { + if (collisionType == 1) { + runLevelScriptCustom(calcNewBlockPosition(_itemsInPlay[t->item].block, t->direction >> 1), 0x8000, -1, t->item, 0, 0); + + } else if (collisionType == 2) { + if (_itemProperties[_itemsInPlay[t->item].itemPropertyIndex].flags & 0x4000) { + uint16 obj = _levelBlockProperties[_itemsInPlay[t->item].block].assignedObjects; + while (obj & 0x8000) { + runItemScript(t->attackerId, t->item, 0x8000, obj, 0); + obj = findObject(obj)->nextAssignedObject; + } + + } else { + runItemScript(t->attackerId, t->item, 0x8000, getNearestMonsterFromPos(x, y), 0); + } + + } else if (collisionType == 4) { + _partyAwake = true; + if (_itemProperties[_itemsInPlay[t->item].itemPropertyIndex].flags & 0x4000) { + for (int i = 0; i < 4; i++) { + if (_characters[i].flags & 1) + runItemScript(t->attackerId, t->item, 0x8000, i, 0); + } + } else { + runItemScript(t->attackerId, t->item, 0x8000, getNearestPartyMemberFromPos(x, y), 0); + } + } +} + +void LoLEngine::updateFlyingObject(FlyingObject *t) { + int x = 0; + int y = 0; + getNextStepCoords(t->x, t->y, x, y, t->direction); + /* WORKAROUND: + Large fireballs cast by the "birds" in white tower level 2 and by the "wraith knights" in castle cimmeria + level 1 (or possible other objects with flag 0x4000) could not fly through corridors in ScummVM and would + be terminated prematurely. The original code (all versions) involuntarily circumvents this via a bug in the + next line of code. + The original checks for _itemProperties[t->item].flags instead of _itemProperties[_itemsInPlay[t->item].itemPropertyIndex].flags. + This leads to more or less unpredictable object widths. The large fireballs will usually get a width of 63 + instead of 256 making them work just fine in the original. + + I have fixed this by setting an object width of 63 of here. This produces results faithful to the original + at least. + + Other methods of working around this issue don't make too much sense. An object with a width of 256 + could never fly through corridors, since 256 is also the width of a block. Aligning the fireballs to the + middle of a block (or making the monsters align to the middle before casting them) wouldn't help here + (and wouldn't be faithful to the original either). + */ + int collisionType = checkBlockBeforeObjectPlacement(x, y, /*_itemProperties[_itemsInPlay[t->item].itemPropertyIndex].flags & 0x4000 ? 256 :*/ 63, t->flags, t->wallFlags); + if (collisionType) { + endObjectFlight(t, x, y, collisionType); + } else { + if (--t->distance) { + processObjectFlight(t, x, y); + } else { + endObjectFlight(t, x, y, 8); + } + } +} + +void LoLEngine::assignItemToBlock(uint16 *assignedBlockObjects, int id) { + while (*assignedBlockObjects & 0x8000) { + LoLObject *tmp = findObject(*assignedBlockObjects); + assignedBlockObjects = &tmp->nextAssignedObject; + } + + LoLObject *newObject = findObject(id); + newObject->nextAssignedObject = *assignedBlockObjects; + ((LoLItem *)newObject)->level = -1; + *assignedBlockObjects = id; +} + +int LoLEngine::checkDrawObjectSpace(int x1, int y1, int x2, int y2) { + int dx = x1 - x2; + if (dx < 0) + dx = -dx; + + int dy = y1 - y2; + if (dy < 0) + dy = -dy; + + return dx + dy; +} + +int LoLEngine::checkSceneForItems(uint16 *blockDrawObjects, int color) { + while (*blockDrawObjects) { + if (!(*blockDrawObjects & 0x8000)) { + if (!--color) + return *blockDrawObjects; + } + + LoLObject *i = findObject(*blockDrawObjects); + blockDrawObjects = &i->nextDrawObject; + } + + return -1; +} + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/items_mr.cpp b/engines/kyra/engine/items_mr.cpp new file mode 100644 index 0000000000..3963934ffb --- /dev/null +++ b/engines/kyra/engine/items_mr.cpp @@ -0,0 +1,536 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_mr.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_MR::removeTrashItems() { + for (int i = 0; _trashItemList[i] != kItemNone; ++i) { + for (int item = findItem(_trashItemList[i]); item != -1; item = findItem(_trashItemList[i])) { + if (_itemList[item].sceneId != _mainCharacter.sceneId) + resetItem(item); + else + break; + } + } +} + +int KyraEngine_MR::findFreeInventorySlot() { + for (int i = 0; i < 10; ++i) { + if (_mainCharacter.inventory[i] == kItemNone) + return i; + } + return -1; +} + +int KyraEngine_MR::checkItemCollision(int x, int y) { + int itemIndex = -1; + int maxItemY = -1; + + for (int i = 0; i < 50; ++i) { + if (_itemList[i].id == kItemNone || _itemList[i].sceneId != _mainCharacter.sceneId) + continue; + + const int x1 = _itemList[i].x - 11; + const int x2 = _itemList[i].x + 10; + + if (x < x1 || x > x2) + continue; + + const int y1 = _itemList[i].y - _itemBuffer1[_itemList[i].id] - 3; + const int y2 = _itemList[i].y + 3; + + if (y < y1 || y > y2) + continue; + + if (_itemList[i].y >= maxItemY) { + itemIndex = i; + maxItemY = _itemList[i].y; + } + } + + return itemIndex; +} + +void KyraEngine_MR::setMouseCursor(Item item) { + int shape = 0; + int hotX = 1; + int hotY = 1; + + if (item != kItemNone) { + hotX = 12; + hotY = 19; + shape = item+248; + } + + _mouseState = item; + if ((int16)item >= 0) + _screen->setMouseCursor(hotX, hotY, getShapePtr(shape)); +} + +void KyraEngine_MR::setItemMouseCursor() { + _mouseState = _itemInHand; + if (_itemInHand == kItemNone) + _screen->setMouseCursor(0, 0, _gameShapes[0]); + else + _screen->setMouseCursor(12, 19, _gameShapes[_itemInHand+248]); +} + +bool KyraEngine_MR::dropItem(int unk1, Item item, int x, int y, int unk2) { + if (_mouseState <= -1) + return false; + + if (processItemDrop(_mainCharacter.sceneId, item, x, y, unk1, unk2)) + return true; + + snd_playSoundEffect(13, 200); + + if (countAllItems() >= 50) { + removeTrashItems(); + if (processItemDrop(_mainCharacter.sceneId, item, x, y, unk1, unk2)) + return true; + + if (countAllItems() >= 50) + showMessageFromCCode(14, 0xB3, 0); + } + + if (!_chatText) + snd_playSoundEffect(13, 200); + return false; +} + +bool KyraEngine_MR::processItemDrop(uint16 sceneId, Item item, int x, int y, int unk1, int unk2) { + int itemPos = checkItemCollision(x, y); + + if (unk1) + itemPos = -1; + + if (itemPos >= 0) { + exchangeMouseItem(itemPos, 1); + return true; + } + + int freeItemSlot = -1; + + if (unk2 != 3) { + for (int i = 0; i < 50; ++i) { + if (_itemList[i].id == kItemNone) { + freeItemSlot = i; + break; + } + } + } + + if (freeItemSlot < 0) + return false; + + if (_mainCharacter.sceneId != sceneId) { + _itemList[freeItemSlot].x = x; + _itemList[freeItemSlot].y = y; + _itemList[freeItemSlot].id = item; + _itemList[freeItemSlot].sceneId = sceneId; + return true; + } + + int itemHeight = _itemBuffer1[item]; + + // no idea why it's '&&' here and not single checks for x and y + if (x == -1 && y == -1) { + x = _rnd.getRandomNumberRng(0x18, 0x128); + y = _rnd.getRandomNumberRng(0x14, 0x87); + } + + int posX = x, posY = y; + int itemX = -1, itemY = -1; + bool needRepositioning = true; + + while (needRepositioning) { + if ((_screen->getDrawLayer(posX, posY) <= 1 && _screen->getDrawLayer2(posX, posY, itemHeight) <= 1 && isDropable(posX, posY)) || posY == 187) { + int posX2 = posX, posX3 = posX; + bool repositioning = true; + + while (repositioning) { + if (isDropable(posX3, posY) && _screen->getDrawLayer2(posX3, posY, itemHeight) < 7 && checkItemCollision(posX3, posY) == -1) { + itemX = posX3; + itemY = posY; + needRepositioning = false; + repositioning = false; + } + + if (isDropable(posX2, posY) && _screen->getDrawLayer2(posX2, posY, itemHeight) < 7 && checkItemCollision(posX2, posY) == -1) { + itemX = posX2; + itemY = posY; + needRepositioning = false; + repositioning = false; + } + + if (repositioning) { + posX3 = MAX(posX3 - 2, 24); + posX2 = MIN(posX2 + 2, 296); + + if (posX3 <= 24 && posX2 >= 296) + repositioning = false; + } + } + } + + if (posY == 187) + needRepositioning = false; + else + posY = MIN(posY + 2, 187); + } + + if (itemX == -1 || itemY == -1) + return false; + + if (unk1 == 3) { + _itemList[freeItemSlot].x = itemX; + _itemList[freeItemSlot].y = itemY; + return true; + } else if (unk1 == 2) { + itemDropDown(x, y, itemX, itemY, freeItemSlot, item, 0); + } + + itemDropDown(x, y, itemX, itemY, freeItemSlot, item, (unk1 == 0) ? 1 : 0); + + if (!unk1 && unk2) { + int itemStr = 1; + if (_lang == 1) + itemStr = getItemCommandStringDrop(item); + updateItemCommand(item, itemStr, 0xFF); + } + + return true; +} + +void KyraEngine_MR::itemDropDown(int startX, int startY, int dstX, int dstY, int itemSlot, Item item, int remove) { + if (startX == dstX && startY == dstY) { + _itemList[itemSlot].x = dstX; + _itemList[itemSlot].y = dstY; + _itemList[itemSlot].id = item; + _itemList[itemSlot].sceneId = _mainCharacter.sceneId; + snd_playSoundEffect(0x0C, 0xC8); + addItemToAnimList(itemSlot); + } else { + uint8 *itemShape = getShapePtr(item + 248); + _screen->hideMouse(); + + if (startY <= dstY) { + int speed = 2; + int curY = startY; + int curX = startX - 12; + + backUpGfxRect32x32(curX, curY-16); + while (curY < dstY) { + restoreGfxRect32x32(curX, curY-16); + + curY = MIN(curY + speed, dstY); + ++speed; + + backUpGfxRect32x32(curX, curY-16); + uint32 endDelay = _system->getMillis() + _tickLength; + + _screen->drawShape(0, itemShape, curX, curY-16, 0, 0); + _screen->updateScreen(); + + delayUntil(endDelay); + } + restoreGfxRect32x32(curX, curY-16); + + if (dstX != dstY || (dstY - startY > 16)) { + snd_playSoundEffect(0x11, 0xC8); + speed = MAX(speed, 6); + int speedX = ((dstX - startX) << 4) / speed; + int origSpeed = speed; + speed >>= 1; + + if (dstY - startY <= 8) + speed >>= 1; + + speed = -speed; + + curX = startX << 4; + + int x = 0, y = 0; + while (--origSpeed) { + curY = MIN(curY + speed, dstY); + curX += speedX; + ++speed; + + x = (curX >> 4) - 8; + y = curY - 16; + backUpGfxRect32x32(x, y); + + uint16 endDelay = _system->getMillis() + _tickLength; + _screen->drawShape(0, itemShape, x, y, 0, 0); + _screen->updateScreen(); + + restoreGfxRect32x32(x, y); + + delayUntil(endDelay); + } + + restoreGfxRect32x32(x, y); + } + } + + _itemList[itemSlot].x = dstX; + _itemList[itemSlot].y = dstY; + _itemList[itemSlot].id = item; + _itemList[itemSlot].sceneId = _mainCharacter.sceneId; + snd_playSoundEffect(0x0C, 0xC8); + addItemToAnimList(itemSlot); + _screen->showMouse(); + } + + if (remove) + removeHandItem(); +} + +void KyraEngine_MR::exchangeMouseItem(int itemPos, int runScript) { + if (itemListMagic(_itemInHand, itemPos)) + return; + + if (_itemInHand == 43) { + removeHandItem(); + return; + } + + deleteItemAnimEntry(itemPos); + + Item itemId = _itemList[itemPos].id; + _itemList[itemPos].id = _itemInHand; + _itemInHand = itemId; + + addItemToAnimList(itemPos); + snd_playSoundEffect(0x0B, 0xC8); + setMouseCursor(_itemInHand); + int str2 = 0; + + if (_lang == 1) + str2 = getItemCommandStringPickUp(itemId); + + updateItemCommand(itemId, str2, 0xFF); + + if (runScript) + runSceneScript6(); +} + +bool KyraEngine_MR::pickUpItem(int x, int y, int runScript) { + int itemPos = checkItemCollision(x, y); + + if (itemPos <= -1) + return false; + + if (_itemInHand >= 0) { + exchangeMouseItem(itemPos, runScript); + } else { + deleteItemAnimEntry(itemPos); + Item itemId = _itemList[itemPos].id; + _itemList[itemPos].id = kItemNone; + snd_playSoundEffect(0x0B, 0xC8); + setMouseCursor(itemId); + int itemString = 0; + + if (_lang == 1) + itemString = getItemCommandStringPickUp(itemId); + + updateItemCommand(itemId, itemString, 0xFF); + _itemInHand = itemId; + + if (runScript) + runSceneScript6(); + } + + return true; +} + +bool KyraEngine_MR::isDropable(int x, int y) { + if (y < 14 || y > 187) + return false; + + x -= 12; + + for (int xpos = x; xpos < x + 24; ++xpos) { + if (_screen->getShapeFlag1(xpos, y) == 0) + return false; + } + + return true; +} + +bool KyraEngine_MR::itemListMagic(Item handItem, int itemSlot) { + Item item = _itemList[itemSlot].id; + + if (_currentChapter == 1 && handItem == 3 && item == 3 && queryGameFlag(0x76)) { + eelScript(); + return true; + } else if ((handItem == 6 || handItem == 7) && item == 2) { + int animObjIndex = -1; + for (int i = 17; i <= 66; ++i) { + if (_animObjects[i].shapeIndex2 == 250) + animObjIndex = i; + } + + assert(animObjIndex != -1); + + snd_playSoundEffect(0x93, 0xC8); + for (int i = 109; i <= 141; ++i) { + _animObjects[animObjIndex].shapeIndex1 = i+248; + _animObjects[animObjIndex].needRefresh = true; + delay(1*_tickLength, true); + } + + deleteItemAnimEntry(itemSlot); + _itemList[itemSlot].id = kItemNone; + return true; + } + + if (_mainCharacter.sceneId == 51 && queryGameFlag(0x19B) && !queryGameFlag(0x19C) + && ((item == 63 && handItem == 56) || (item == 56 && handItem == 63))) { + + if (queryGameFlag(0x1AC)) { + setGameFlag(0x19C); + setGameFlag(0x1AD); + } else { + setGameFlag(0x1AE); + } + + _timer->setCountdown(12, 1); + _timer->enable(12); + } + + for (int i = 0; _itemMagicTable[i] != 0xFF; i += 4) { + if (_itemMagicTable[i+0] != handItem || (int8)_itemMagicTable[i+1] != item) + continue; + + uint8 resItem = _itemMagicTable[i+2]; + uint8 newItem = _itemMagicTable[i+3]; + + snd_playSoundEffect(0x0F, 0xC8); + + _itemList[itemSlot].id = (int8)resItem; + + deleteItemAnimEntry(itemSlot); + addItemToAnimList(itemSlot); + + if (newItem == 0xFE) + removeHandItem(); + else if (newItem != 0xFF) + setHandItem(newItem); + + if (_lang != 1) + updateItemCommand(resItem, 3, 0xFF); + + // Unlike the original we give points for when combining with scene items + if (resItem == 7) { + updateScore(35, 100); + delay(60*_tickLength, true); + } + + return true; + } + + return false; +} + +bool KyraEngine_MR::itemInventoryMagic(Item handItem, int invSlot) { + Item item = _mainCharacter.inventory[invSlot]; + + if (_currentChapter == 1 && handItem == 3 && item == 3 && queryGameFlag(0x76)) { + eelScript(); + return true; + } else if ((handItem == 6 || handItem == 7) && item == 2) { + _screen->hideMouse(); + snd_playSoundEffect(0x93, 0xC8); + for (int i = 109; i <= 141; ++i) { + _mainCharacter.inventory[invSlot] = i; + _screen->drawShape(2, getShapePtr(invSlot+422), 0, 144, 0, 0); + _screen->drawShape(2, getShapePtr(i+248), 0, 144, 0, 0); + _screen->copyRegion(0, 144, _inventoryX[invSlot], _inventoryY[invSlot], 24, 20, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + delay(1*_tickLength, true); + } + + _mainCharacter.inventory[invSlot] = kItemNone; + clearInventorySlot(invSlot, 0); + _screen->showMouse(); + return true; + } + + for (int i = 0; _itemMagicTable[i] != 0xFF; i += 4) { + if (_itemMagicTable[i+0] != handItem || _itemMagicTable[i+1] != item) + continue; + + uint8 resItem = _itemMagicTable[i+2]; + uint8 newItem = _itemMagicTable[i+3]; + + snd_playSoundEffect(0x0F, 0xC8); + + _mainCharacter.inventory[invSlot] = (int8)resItem; + + clearInventorySlot(invSlot, 0); + drawInventorySlot(0, resItem, invSlot); + + if (newItem == 0xFE) + removeHandItem(); + else if (newItem != 0xFF) + setHandItem(newItem); + + if (_lang != 1) + updateItemCommand(resItem, 3, 0xFF); + + // Unlike the original we give points for every language + if (resItem == 7) { + updateScore(35, 100); + delay(60*_tickLength, true); + } + + return true; + } + + return false; +} + +int KyraEngine_MR::getItemCommandStringDrop(uint16 item) { + assert(item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + return _itemStringDrop[stringId]; +} + +int KyraEngine_MR::getItemCommandStringPickUp(uint16 item) { + assert(item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + return _itemStringPickUp[stringId]; +} + +int KyraEngine_MR::getItemCommandStringInv(uint16 item) { + assert(item < _itemStringMapSize); + int stringId = _itemStringMap[item]; + return _itemStringInv[stringId]; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/items_v2.cpp b/engines/kyra/engine/items_v2.cpp new file mode 100644 index 0000000000..93afff62aa --- /dev/null +++ b/engines/kyra/engine/items_v2.cpp @@ -0,0 +1,100 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_v2.h" +#include "kyra/graphics/screen_v2.h" + +namespace Kyra { + +void KyraEngine_v2::initItemList(int size) { + delete[] _itemList; + + _itemList = new ItemDefinition[size]; + assert(_itemList); + memset(_itemList, 0, sizeof(ItemDefinition)*size); + _itemListSize = size; + + resetItemList(); +} + +int KyraEngine_v2::findFreeItem() { + for (int i = 0; i < _itemListSize; ++i) { + if (_itemList[i].id == kItemNone) + return i; + } + return -1; +} + +int KyraEngine_v2::countAllItems() { + int num = 0; + for (int i = 0; i < _itemListSize; ++i) { + if (_itemList[i].id != kItemNone) + ++num; + } + return num; +} + +int KyraEngine_v2::findItem(uint16 sceneId, Item id) { + for (int i = 0; i < _itemListSize; ++i) { + if (_itemList[i].id == id && _itemList[i].sceneId == sceneId) + return i; + } + return -1; +} + +int KyraEngine_v2::findItem(Item item) { + for (int i = 0; i < _itemListSize; ++i) { + if (_itemList[i].id == item) + return i; + } + return -1; +} + +void KyraEngine_v2::resetItemList() { + for (int i = 0; i < _itemListSize; ++i) + resetItem(i); +} + +void KyraEngine_v2::resetItem(int index) { + _itemList[index].id = kItemNone; + _itemList[index].sceneId = 0xFFFF; + _itemList[index].x = 0; + _itemList[index].y = 0; +} + +void KyraEngine_v2::setHandItem(Item item) { + if (item == kItemNone) { + removeHandItem(); + } else { + setMouseCursor(item); + _itemInHand = item; + } +} + +void KyraEngine_v2::removeHandItem() { + Screen *scr = screen(); + scr->setMouseCursor(0, 0, getShapePtr(0)); + _itemInHand = kItemNone; + _mouseState = kItemNone; +} + +} // end of namesapce Kyra diff --git a/engines/kyra/engine/kyra_hof.cpp b/engines/kyra/engine/kyra_hof.cpp new file mode 100644 index 0000000000..94eca126ec --- /dev/null +++ b/engines/kyra/engine/kyra_hof.cpp @@ -0,0 +1,1933 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_hof.h" +#include "kyra/resource/resource.h" +#include "kyra/text/text_hof.h" +#include "kyra/engine/timer.h" +#include "kyra/gui/debugger.h" +#include "kyra/engine/util.h" +#include "kyra/sound/sound.h" + +#include "common/system.h" +#include "common/config-manager.h" + +namespace Kyra { + +const KyraEngine_v2::EngineDesc KyraEngine_HoF::_hofEngineDesc = { + // Generic shape related + 64, + KyraEngine_HoF::_characterFrameTable, + + // Scene script + 8, + + // Animation script specific + 33, + + // Item specific + 175 +}; + +KyraEngine_HoF::KyraEngine_HoF(OSystem *system, const GameFlags &flags) : KyraEngine_v2(system, flags, _hofEngineDesc), _updateFunctor(this, &KyraEngine_HoF::update) { + _screen = 0; + _text = 0; + + _gamePlayBuffer = 0; + _cCodeBuffer = _optionsBuffer = _chapterBuffer = 0; + + _overwriteSceneFacing = false; + _mainCharX = _mainCharY = -1; + _drawNoShapeFlag = false; + _charPalEntry = 0; + _itemInHand = kItemNone; + _unkSceneScreenFlag1 = false; + _noScriptEnter = true; + _currentChapter = 0; + _newChapterFile = 1; + _oldTalkFile = -1; + _currentTalkFile = 0; + _lastSfxTrack = -1; + _mouseState = -1; + _unkHandleSceneChangeFlag = false; + _pathfinderFlag = 0; + _mouseX = _mouseY = 0; + + _nextIdleAnim = 0; + _lastIdleScript = -1; + _useSceneIdleAnim = false; + + _currentTalkSections.STATim = 0; + _currentTalkSections.TLKTim = 0; + _currentTalkSections.ENDTim = 0; + + memset(&_invWsa, 0, sizeof(_invWsa)); + _itemAnimDefinition = 0; + _nextAnimItem = 0; + + for (int i = 0; i < 15; i++) + memset(&_activeItemAnim[i], 0, sizeof(ActiveItemAnim)); + + _colorCodeFlag1 = 0; + _colorCodeFlag2 = -1; + _scriptCountDown = 0; + _dbgPass = 0; + + _gamePlayBuffer = 0; + _unkBuf500Bytes = 0; + _inventorySaved = false; + _unkBuf200kByte = 0; + memset(&_sceneShapeTable, 0, sizeof(_sceneShapeTable)); + + _talkObjectList = 0; + _shapeDescTable = 0; + _gfxBackUpRect = 0; + _sceneList = 0; + memset(&_sceneAnimMovie, 0, sizeof(_sceneAnimMovie)); + memset(&_wsaSlots, 0, sizeof(_wsaSlots)); + memset(&_buttonShapes, 0, sizeof(_buttonShapes)); + + _configTextspeed = 50; + + _inventoryButtons = _buttonList = 0; + + _dlgBuffer = 0; + _conversationState = new int8 *[19]; + for (int i = 0; i < 19; i++) + _conversationState[i] = new int8[14]; + _npcTalkChpIndex = _npcTalkDlgIndex = -1; + _mainCharacter.dlgIndex = 0; + setDlgIndex(-1); + + _bookMaxPage = 6; + _bookCurPage = 0; + _bookNewPage = 0; + _bookBkgd = 0; + + _cauldronState = 0; + _cauldronUseCount = 0; + memset(_cauldronStateTables, 0, sizeof(_cauldronStateTables)); + + _menuDirectlyToLoad = false; + _chatIsNote = false; + memset(&_npcScriptData, 0, sizeof(_npcScriptData)); + + _setCharPalFinal = false; + _useCharPal = false; + + memset(_characterFacingCountTable, 0, sizeof(_characterFacingCountTable)); +} + +KyraEngine_HoF::~KyraEngine_HoF() { + cleanup(); + + delete _screen; + delete _text; + delete _gui; + delete _tim; + _text = 0; + delete _invWsa.wsa; + + delete[] _dlgBuffer; + for (int i = 0; i < 19; i++) + delete[] _conversationState[i]; + delete[] _conversationState; + + for (Common::Array::iterator i = _timOpcodes.begin(); i != _timOpcodes.end(); ++i) + delete *i; + _timOpcodes.clear(); +} + +void KyraEngine_HoF::pauseEngineIntern(bool pause) { + KyraEngine_v2::pauseEngineIntern(pause); + + seq_pausePlayer(pause); + + if (!pause) { + uint32 pausedTime = _system->getMillis() - _pauseStart; + _pauseStart = 0; + + _nextIdleAnim += pausedTime; + _tim->refreshTimersAfterPause(pausedTime); + } +} + +Common::Error KyraEngine_HoF::init() { + _screen = new Screen_HoF(this, _system); + assert(_screen); + _screen->setResolution(); + + _debugger = new Debugger_HoF(this); + assert(_debugger); + + KyraEngine_v1::init(); + initStaticResource(); + + _text = new TextDisplayer_HoF(this, _screen); + assert(_text); + _gui = new GUI_HoF(this); + assert(_gui); + _gui->initStaticData(); + _tim = new TIMInterpreter(this, _screen, _system); + assert(_tim); + + if (_flags.isDemo && !_flags.isTalkie) { + _screen->loadFont(_screen->FID_8_FNT, "FONT9P.FNT"); + } else { + _screen->loadFont(_screen->FID_6_FNT, "6.FNT"); + _screen->loadFont(_screen->FID_8_FNT, "8FAT.FNT"); + _screen->loadFont(_screen->FID_BOOKFONT_FNT, "BOOKFONT.FNT"); + } + _screen->setFont(_flags.lang == Common::JA_JPN ? Screen::FID_SJIS_FNT : _screen->FID_8_FNT); + + _screen->setAnimBlockPtr(3504); + _screen->setScreenDim(0); + + if (!_sound->init()) + error("Couldn't init sound"); + + // No mouse display in demo + if (_flags.isDemo && !_flags.isTalkie) + return Common::kNoError; + + _res->exists("PWGMOUSE.SHP", true); + uint8 *shapes = _res->fileData("PWGMOUSE.SHP", 0); + assert(shapes); + + for (int i = 0; i < 2; i++) + addShapeToPool(shapes, i, i); + + delete[] shapes; + + _screen->setMouseCursor(0, 0, getShapePtr(0)); + return Common::kNoError; +} + +Common::Error KyraEngine_HoF::go() { + int menuChoice = 0; + + if (_gameToLoad == -1) { + if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) + seq_showStarcraftLogo(); + + if (_flags.isDemo && !_flags.isTalkie) { + menuChoice = seq_playDemo(); + } else { + menuChoice = seq_playIntro(); + } + } else { + menuChoice = 1; + } + + _res->unloadAllPakFiles(); + + if (menuChoice != 4) { + // load just the pak files needed for ingame + _staticres->loadStaticResourceFile(); + + if (_flags.platform == Common::kPlatformDOS && _flags.isTalkie) { + if (!_res->loadFileList("FILEDATA.FDT")) + error("couldn't load 'FILEDATA.FDT'"); + } else { + _res->loadFileList(_ingamePakList, _ingamePakListSize); + } + + if (_flags.platform == Common::kPlatformPC98) { + _res->loadPakFile("AUDIO.PAK"); + _sound->loadSoundFile("SOUND.DAT"); + } + } + + _menuDirectlyToLoad = (menuChoice == 3) ? true : false; + _menuDirectlyToLoad &= saveFileLoadable(0); + + if (menuChoice & 1) { + startup(); + if (!shouldQuit()) + runLoop(); + cleanup(); + + if (_showOutro) + seq_playOutro(); + } + + return Common::kNoError; +} + +void KyraEngine_HoF::startup() { + _sound->selectAudioResourceSet(kMusicIngame); + // The track map is exactly the same + // for FM-TOWNS and DOS + _trackMap = _dosTrackMap; + _trackMapSize = _dosTrackMapSize; + + allocAnimObjects(1, 10, 30); + + _screen->_curPage = 0; + + memset(_sceneShapeTable, 0, sizeof(_sceneShapeTable)); + _gamePlayBuffer = new uint8[46080]; + _unkBuf500Bytes = new uint8[500]; + + loadMouseShapes(); + loadItemShapes(); + + _screen->setMouseCursor(0, 0, getShapePtr(0)); + + _screenBuffer = new uint8[64000]; + _unkBuf200kByte = new uint8[200000]; + + loadChapterBuffer(_newChapterFile); + + loadCCodeBuffer("C_CODE.XXX"); + + if (_flags.isTalkie) { + loadOptionsBuffer("OPTIONS.XXX"); + + showMessageFromCCode(265, 150, 0); + _screen->updateScreen(); + openTalkFile(0); + _currentTalkFile = 1; + openTalkFile(1); + } else { + _optionsBuffer = _cCodeBuffer; + } + + showMessage(0, 207); + + _screen->setShapePages(5, 3); + + _mainCharacter.height = 0x30; + _mainCharacter.facing = 4; + _mainCharacter.animFrame = 0x12; + + memset(_sceneAnims, 0, sizeof(_sceneAnims)); + for (int i = 0; i < ARRAYSIZE(_sceneAnimMovie); ++i) + _sceneAnimMovie[i] = new WSAMovie_v2(this); + memset(_wsaSlots, 0, sizeof(_wsaSlots)); + for (int i = 0; i < ARRAYSIZE(_wsaSlots); ++i) + _wsaSlots[i] = new WSAMovie_v2(this); + + _screen->_curPage = 0; + + _talkObjectList = new TalkObject[72]; + memset(_talkObjectList, 0, sizeof(TalkObject)*72); + _shapeDescTable = new ShapeDesc[55]; + memset(_shapeDescTable, 0, sizeof(ShapeDesc)*55); + + for (int i = 9; i <= 32; ++i) { + _shapeDescTable[i-9].width = 30; + _shapeDescTable[i-9].height = 55; + _shapeDescTable[i-9].xAdd = -15; + _shapeDescTable[i-9].yAdd = -50; + } + + for (int i = 19; i <= 24; ++i) { + _shapeDescTable[i-9].width = 53; + _shapeDescTable[i-9].yAdd = -51; + } + + _gfxBackUpRect = new uint8[_screen->getRectSize(32, 32)]; + initItemList(30); + loadButtonShapes(); + resetItemList(); + _characterShapeFile = 1; + loadCharacterShapes(_characterShapeFile); + initInventoryButtonList(); + setupLangButtonShapes(); + loadInventoryShapes(); + + _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); + _screen->loadBitmap("_PLAYFLD.CPS", 3, 3, 0); + _screen->copyPage(3, 0); + + clearAnimObjects(); + + for (int i = 0; i < 19; ++i) + memset(_conversationState[i], -1, sizeof(int8)*14); + clearCauldronTable(); + memset(_inputColorCode, -1, sizeof(_inputColorCode)); + memset(_newSceneDlgState, 0, sizeof(_newSceneDlgState)); + for (int i = 0; i < 23; ++i) + resetCauldronStateTable(i); + + _sceneList = new SceneDesc[86]; + memset(_sceneList, 0, sizeof(SceneDesc)*86); + _sceneListSize = 86; + runStartScript(1, 0); + loadNPCScript(); + + if (_gameToLoad == -1) { + snd_playWanderScoreViaMap(52, 1); + enterNewScene(_mainCharacter.sceneId, _mainCharacter.facing, 0, 0, 1); + saveGameStateIntern(0, "New Game", 0); + } else { + loadGameStateCheck(_gameToLoad); + } + + _screen->showMouse(); + + if (_menuDirectlyToLoad) + (*_inventoryButtons[0].buttonCallback)(&_inventoryButtons[0]); + + setNextIdleAnimTimer(); + setWalkspeed(_configWalkspeed); +} + +void KyraEngine_HoF::runLoop() { + // Initialize debugger since how it should be fully usable + _debugger->initialize(); + + _screen->updateScreen(); + + _runFlag = true; + while (!shouldQuit() && _runFlag) { + if (_deathHandler >= 0) { + removeHandItem(); + delay(5); + _drawNoShapeFlag = 0; + _gui->optionsButton(0); + _deathHandler = -1; + + if (!_runFlag || shouldQuit()) + break; + } + + if (_system->getMillis() > _nextIdleAnim) + showIdleAnim(); + + if (queryGameFlag(0x159)) { + dinoRide(); + resetGameFlag(0x159); + } + + if (queryGameFlag(0x124) && !queryGameFlag(0x125)) { + _mainCharacter.animFrame = 32; + enterNewScene(39, -1, 0, 0, 0); + } + + if (queryGameFlag(0xD8)) { + resetGameFlag(0xD8); + if (_mainCharacter.sceneId == 34) { + if (queryGameFlag(0xD1)) { + initTalkObject(28); + npcChatSequence(getTableString(0xFA, _cCodeBuffer, 1), 28, 0x83, 0xFA); + deinitTalkObject(28); + enterNewScene(35, 4, 0, 0, 0); + } else if (queryGameFlag(0xD0)) { + initTalkObject(29); + npcChatSequence(getTableString(0xFB, _cCodeBuffer, 1), 29, 0x83, 0xFB); + deinitTalkObject(29); + enterNewScene(33, 6, 0, 0, 0); + } + } + } + + int inputFlag = checkInput(_buttonList, true); + removeInputTop(); + + update(); + + if (inputFlag == 198 || inputFlag == 199) { + _savedMouseState = _mouseState; + handleInput(_mouseX, _mouseY); + } + + //if (queryGameFlag(0x1EE) && inputFlag) + // sub_13B19(inputFlag); + + _system->delayMillis(10); + } +} + +void KyraEngine_HoF::handleInput(int x, int y) { + setNextIdleAnimTimer(); + if (_unk5) { + _unk5 = 0; + return; + } + + if (!_screen->isMouseVisible()) + return; + + if (_savedMouseState == -2) { + snd_playSoundEffect(13); + return; + } + + setNextIdleAnimTimer(); + + if (x <= 6 || x >= 312 || y <= 6 || y >= 135) { + bool exitOk = false; + assert(_savedMouseState + 6 >= 0); + switch (_savedMouseState + 6) { + case 0: + if (_sceneExit1 != 0xFFFF) + exitOk = true; + break; + + case 1: + if (_sceneExit2 != 0xFFFF) + exitOk = true; + break; + + case 2: + if (_sceneExit3 != 0xFFFF) + exitOk = true; + break; + + case 3: + if (_sceneExit4 != 0xFFFF) + exitOk = true; + break; + + default: + break; + } + + if (exitOk) { + inputSceneChange(x, y, 1, 1); + return; + } + } + + if (checkCharCollision(x, y) && _savedMouseState >= -1) { + runSceneScript2(); + return; + } else if (pickUpItem(x, y)) { + return; + } else { + int skipHandling = 0; + + if (checkItemCollision(x, y) == -1) { + resetGameFlag(0x1EF); + skipHandling = handleInputUnkSub(x, y) ? 1 : 0; + + if (queryGameFlag(0x1EF)) { + resetGameFlag(0x1EF); + return; + } + + if (_unk5) { + _unk5 = 0; + return; + } + } + + if (_deathHandler > -1) + skipHandling = 1; + + if (skipHandling) + return; + + if (checkCharCollision(x, y)) { + runSceneScript2(); + return; + } + + if (_itemInHand >= 0) { + if (y > 136) + return; + + dropItem(0, _itemInHand, x, y, 1); + } else { + if (_savedMouseState == -2 || y > 135) + return; + + if (!_unk5) { + inputSceneChange(x, y, 1, 1); + return; + } + + _unk5 = 0; + } + } +} + +bool KyraEngine_HoF::handleInputUnkSub(int x, int y) { + if (y > 143 || _deathHandler > -1 || queryGameFlag(0x164)) + return false; + + if (_mouseState <= -3 && findItem(_mainCharacter.sceneId, 13) >= 0) { + updateCharFacing(); + objectChat(getTableString(0xFC, _cCodeBuffer, 1), 0, 0x83, 0xFC); + return true; + } else { + _emc->init(&_sceneScriptState, &_sceneScriptData); + + _sceneScriptState.regs[1] = x; + _sceneScriptState.regs[2] = y; + _sceneScriptState.regs[3] = 0; + _sceneScriptState.regs[4] = _itemInHand; + + _emc->start(&_sceneScriptState, 1); + + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + if (queryGameFlag(0x1ED)) { + _sound->beginFadeOut(); + _screen->fadeToBlack(); + _showOutro = true; + _runFlag = false; + } + + return _sceneScriptState.regs[3] != 0; + } +} + +void KyraEngine_HoF::update() { + updateInput(); + + refreshAnimObjectsIfNeed(); + updateMouse(); + updateSpecialSceneScripts(); + _timer->update(); + updateItemAnimations(); + updateInvWsa(); + fadeMessagePalette(); + _screen->updateScreen(); +} + +void KyraEngine_HoF::updateWithText() { + updateInput(); + + updateMouse(); + fadeMessagePalette(); + updateSpecialSceneScripts(); + _timer->update(); + updateItemAnimations(); + updateInvWsa(); + restorePage3(); + drawAnimObjects(); + + if (_chatTextEnabled && _chatText) { + int pageBackUp = _screen->_curPage; + _screen->_curPage = 2; + objectChatPrintText(_chatText, _chatObject); + _screen->_curPage = pageBackUp; + } + + refreshAnimObjects(0); + _screen->updateScreen(); +} + +void KyraEngine_HoF::updateMouse() { + int shapeIndex = 0; + int type = 0; + int xOffset = 0, yOffset = 0; + Common::Point mouse = getMousePos(); + + if (mouse.y <= 145) { + if (mouse.x <= 6) { + if (_sceneExit4 != 0xFFFF) { + type = -3; + shapeIndex = 4; + xOffset = 1; + yOffset = 5; + } else { + type = -2; + } + } else if (mouse.x >= 312) { + if (_sceneExit2 != 0xFFFF) { + type = -5; + shapeIndex = 2; + xOffset = 7; + yOffset = 5; + } else { + type = -2; + } + } else if (mouse.y >= 135) { + if (_sceneExit3 != 0xFFFF) { + type = -4; + shapeIndex = 3; + xOffset = 5; + yOffset = 10; + } else { + type = -2; + } + } else if (mouse.y <= 6) { + if (_sceneExit1 != 0xFFFF) { + type = -6; + shapeIndex = 1; + xOffset = 5; + yOffset = 1; + } else { + type = -2; + } + } + } + + for (int i = 0; i < _specialExitCount; ++i) { + if (checkSpecialSceneExit(i, mouse.x, mouse.y)) { + switch (_specialExitTable[20+i]) { + case 0: + type = -6; + shapeIndex = 1; + xOffset = 5; + yOffset = 1; + break; + + case 2: + type = -5; + shapeIndex = 2; + xOffset = 7; + yOffset = 5; + break; + + case 4: + type = -4; + shapeIndex = 3; + xOffset = 5; + yOffset = 7; + break; + + case 6: + type = -3; + shapeIndex = 4; + xOffset = 1; + yOffset = 5; + break; + + default: + break; + } + } + } + + if (type == -2) { + shapeIndex = 5; + xOffset = 5; + yOffset = 9; + } + + if (type != 0 && _mouseState != type && _screen->isMouseVisible()) { + _mouseState = type; + _screen->setMouseCursor(xOffset, yOffset, getShapePtr(shapeIndex)); + } + + if (type == 0 && _mouseState != _itemInHand && _screen->isMouseVisible()) { + if ((mouse.y > 145) || (mouse.x > 6 && mouse.x < 312 && mouse.y > 6 && mouse.y < 135)) { + _mouseState = _itemInHand; + if (_itemInHand == kItemNone) + _screen->setMouseCursor(0, 0, getShapePtr(0)); + else + _screen->setMouseCursor(8, 15, getShapePtr(_itemInHand+64)); + } + } +} + +void KyraEngine_HoF::cleanup() { + delete[] _inventoryButtons; _inventoryButtons = 0; + + delete[] _gamePlayBuffer; _gamePlayBuffer = 0; + delete[] _unkBuf500Bytes; _unkBuf500Bytes = 0; + delete[] _unkBuf200kByte; _unkBuf200kByte = 0; + + freeSceneShapePtrs(); + + if (_optionsBuffer != _cCodeBuffer) + delete[] _optionsBuffer; + _optionsBuffer = 0; + delete[] _cCodeBuffer; _cCodeBuffer = 0; + delete[] _chapterBuffer; _chapterBuffer = 0; + + delete[] _talkObjectList; _talkObjectList = 0; + delete[] _shapeDescTable; _shapeDescTable = 0; + + delete[] _gfxBackUpRect; _gfxBackUpRect = 0; + + for (int i = 0; i < ARRAYSIZE(_sceneAnimMovie); ++i) { + delete _sceneAnimMovie[i]; + _sceneAnimMovie[i] = 0; + } + for (int i = 0; i < ARRAYSIZE(_wsaSlots); ++i) { + delete _wsaSlots[i]; + _wsaSlots[i] = 0; + } + for (int i = 0; i < ARRAYSIZE(_buttonShapes); ++i) { + delete[] _buttonShapes[i]; + _buttonShapes[i] = 0; + } + + _emc->unload(&_npcScriptData); +} + +#pragma mark - Localization + +void KyraEngine_HoF::loadCCodeBuffer(const char *file) { + char tempString[13]; + strcpy(tempString, file); + changeFileExtension(tempString); + + delete[] _cCodeBuffer; + _cCodeBuffer = _res->fileData(tempString, 0); +} + +void KyraEngine_HoF::loadOptionsBuffer(const char *file) { + char tempString[13]; + strcpy(tempString, file); + changeFileExtension(tempString); + + delete[] _optionsBuffer; + _optionsBuffer = _res->fileData(tempString, 0); +} + +void KyraEngine_HoF::loadChapterBuffer(int chapter) { + char tempString[14]; + + static const char *const chapterFilenames[] = { + "CH1.XXX", "CH2.XXX", "CH3.XXX", "CH4.XXX", "CH5.XXX" + }; + + assert(chapter >= 1 && chapter <= ARRAYSIZE(chapterFilenames)); + strcpy(tempString, chapterFilenames[chapter-1]); + changeFileExtension(tempString); + + delete[] _chapterBuffer; + _chapterBuffer = _res->fileData(tempString, 0); + _currentChapter = chapter; +} + +void KyraEngine_HoF::changeFileExtension(char *buffer) { + while (*buffer != '.') + ++buffer; + + ++buffer; + strcpy(buffer, _languageExtension[_lang]); +} + +uint8 *KyraEngine_HoF::getTableEntry(uint8 *buffer, int id) { + return buffer + READ_LE_UINT16(buffer + (id<<1)); +} + +char *KyraEngine_HoF::getTableString(int id, uint8 *buffer, int decode) { + char *string = (char *)getTableEntry(buffer, id); + + if (decode && _flags.lang != Common::JA_JPN) { + Util::decodeString1(string, _internStringBuf); + Util::decodeString2(_internStringBuf, _internStringBuf); + string = _internStringBuf; + } + + return string; +} + +const char *KyraEngine_HoF::getChapterString(int id) { + if (_currentChapter != _newChapterFile) + loadChapterBuffer(_newChapterFile); + + return getTableString(id, _chapterBuffer, 1); +} + +#pragma mark - + +void KyraEngine_HoF::showMessageFromCCode(int id, int16 palIndex, int) { + const char *string = getTableString(id, _cCodeBuffer, 1); + showMessage(string, palIndex); +} + +void KyraEngine_HoF::showMessage(const char *string, int16 palIndex) { + _shownMessage = string; + _screen->fillRect(0, 190, 319, 199, 0xCF); + + if (string) { + if (palIndex != -1 || _fadeMessagePalette) { + palIndex *= 3; + memcpy(_messagePal, _screen->getPalette(0).getData() + palIndex, 3); + _screen->getPalette(0).copy(_screen->getPalette(0), palIndex / 3, 1, 255); + _screen->setScreenPalette(_screen->getPalette(0)); + } + + int x = _text->getCenterStringX(string, 0, 320); + _text->printText(string, x, 190, 255, 207, 0); + + setTimer1DelaySecs(7); + } + + _fadeMessagePalette = false; +} + +void KyraEngine_HoF::showChapterMessage(int id, int16 palIndex) { + showMessage(getChapterString(id), palIndex); +} + +void KyraEngine_HoF::updateCommandLineEx(int str1, int str2, int16 palIndex) { + char buffer[0x51]; + char *src = buffer; + + strcpy(src, getTableString(str1, _cCodeBuffer, 1)); + + if (_flags.lang != Common::JA_JPN) { + while (*src != 0x20) + ++src; + ++src; + *src = toupper(*src); + } + + strcpy((char *)_unkBuf500Bytes, src); + + if (str2 > 0) { + if (_flags.lang != Common::JA_JPN) + strcat((char *)_unkBuf500Bytes, " "); + strcat((char *)_unkBuf500Bytes, getTableString(str2, _cCodeBuffer, 1)); + } + + showMessage((char *)_unkBuf500Bytes, palIndex); +} + +void KyraEngine_HoF::fadeMessagePalette() { + if (!_fadeMessagePalette) + return; + + bool updatePalette = false; + for (int i = 0; i < 3; ++i) { + if (_messagePal[i] >= 4) { + _messagePal[i] -= 4; + updatePalette = true; + } else if (_messagePal[i] != 0) { + _messagePal[i] = 0; + updatePalette = true; + } + } + + if (updatePalette) { + _screen->getPalette(0).copy(_messagePal, 0, 1, 255); + _screen->setScreenPalette(_screen->getPalette(0)); + } else { + _fadeMessagePalette = false; + } +} + +#pragma mark - + +void KyraEngine_HoF::loadMouseShapes() { + _screen->loadBitmap("_MOUSE.CSH", 3, 3, 0); + + for (int i = 0; i <= 8; ++i) + addShapeToPool(_screen->getCPagePtr(3), i, i); +} + +void KyraEngine_HoF::loadItemShapes() { + _screen->loadBitmap("_ITEMS.CSH", 3, 3, 0); + + for (int i = 64; i <= 239; ++i) + addShapeToPool(_screen->getCPagePtr(3), i, i-64); + + _res->loadFileToBuf("_ITEMHT.DAT", _itemHtDat, sizeof(_itemHtDat)); + assert(_res->getFileSize("_ITEMHT.DAT") == sizeof(_itemHtDat)); + + _screen->_curPage = 0; +} + +void KyraEngine_HoF::loadCharacterShapes(int shapes) { + char file[10]; + strcpy(file, "_ZX.SHP"); + + _characterShapeFile = shapes; + file[2] = '0' + shapes; + + uint8 *data = _res->fileData(file, 0); + for (int i = 9; i <= 32; ++i) + addShapeToPool(data, i, i-9); + delete[] data; + + _characterShapeFile = shapes; +} + +void KyraEngine_HoF::loadInventoryShapes() { + int curPageBackUp = _screen->_curPage; + _screen->_curPage = 2; + + _screen->loadBitmap("_PLAYALL.CPS", 3, 3, 0); + + for (int i = 0; i < 10; ++i) + addShapeToPool(_screen->encodeShape(_inventoryX[i], _inventoryY[i], 16, 16, 0), 240+i); + + _screen->_curPage = curPageBackUp; +} + +void KyraEngine_HoF::runStartScript(int script, int unk1) { + char filename[14]; + strcpy(filename, "_START0X.EMC"); + filename[7] = script + '0'; + + EMCData scriptData; + EMCState scriptState; + memset(&scriptData, 0, sizeof(EMCData)); + memset(&scriptState, 0, sizeof(EMCState)); + + _emc->load(filename, &scriptData, &_opcodes); + _emc->init(&scriptState, &scriptData); + scriptState.regs[6] = unk1; + _emc->start(&scriptState, 0); + while (_emc->isValid(&scriptState)) + _emc->run(&scriptState); + _emc->unload(&scriptData); +} + +void KyraEngine_HoF::loadNPCScript() { + _emc->unload(&_npcScriptData); + + char filename[] = "_NPC.EMC"; + + if (_flags.platform != Common::kPlatformDOS || _flags.isTalkie) { + switch (_lang) { + case 0: + filename[5] = 'E'; + break; + + case 1: + filename[5] = 'F'; + break; + + case 2: + filename[5] = 'G'; + break; + + case 3: + filename[5] = 'J'; + break; + + default: + break; + }; + } + + _emc->load(filename, &_npcScriptData, &_opcodes); +} + +#pragma mark - + +void KyraEngine_HoF::resetScaleTable() { + Common::fill(_scaleTable, ARRAYEND(_scaleTable), 0x100); +} + +void KyraEngine_HoF::setScaleTableItem(int item, int data) { + if (item >= 1 && item <= 15) + _scaleTable[item-1] = (data << 8) / 100; +} + +int KyraEngine_HoF::getScale(int x, int y) { + return _scaleTable[_screen->getLayer(x, y) - 1]; +} + +void KyraEngine_HoF::setDrawLayerTableEntry(int entry, int data) { + if (entry >= 1 && entry <= 15) + _drawLayerTable[entry-1] = data; +} + +int KyraEngine_HoF::getDrawLayer(int x, int y) { + int layer = _screen->getLayer(x, y); + layer = _drawLayerTable[layer-1]; + if (layer < 0) + layer = 0; + else if (layer >= 7) + layer = 6; + return layer; +} + +void KyraEngine_HoF::backUpPage0() { + if (_screenBuffer) { + memcpy(_screenBuffer, _screen->getCPagePtr(0), 64000); + } +} + +void KyraEngine_HoF::restorePage0() { + restorePage3(); + if (_screenBuffer) + _screen->copyBlockToPage(0, 0, 0, 320, 200, _screenBuffer); +} + +void KyraEngine_HoF::updateCharPal(int unk1) { + if (!_useCharPal) + return; + + int layer = _screen->getLayer(_mainCharacter.x1, _mainCharacter.y1); + int palEntry = _charPalTable[layer]; + + if (palEntry != _charPalEntry && unk1) { + const uint8 *src = &_scenePal[(palEntry << 4) * 3]; + uint8 *ptr = _screen->getPalette(0).getData() + 336; + for (int i = 0; i < 48; ++i) { + *ptr -= (*ptr - *src) >> 1; + ++ptr; + ++src; + } + _screen->setScreenPalette(_screen->getPalette(0)); + _setCharPalFinal = true; + _charPalEntry = palEntry; + } else if (_setCharPalFinal || !unk1) { + _screen->getPalette(0).copy(_scenePal, palEntry << 4, 16, 112); + _screen->setScreenPalette(_screen->getPalette(0)); + _setCharPalFinal = false; + } +} + +void KyraEngine_HoF::setCharPalEntry(int entry, int value) { + if (entry > 15 || entry < 1) + entry = 1; + if (value > 8 || value < 0) + value = 0; + _charPalTable[entry] = value; + _useCharPal = 1; + _charPalEntry = 0; +} + +int KyraEngine_HoF::inputSceneChange(int x, int y, int unk1, int unk2) { + bool refreshNPC = false; + uint16 curScene = _mainCharacter.sceneId; + _pathfinderFlag = 15; + + if (!_unkHandleSceneChangeFlag) { + if (_savedMouseState == -3) { + if (_sceneList[curScene].exit4 != 0xFFFF) { + x = 4; + y = _sceneEnterY4; + _pathfinderFlag = 7; + } + } else if (_savedMouseState == -5) { + if (_sceneList[curScene].exit2 != 0xFFFF) { + x = 316; + y = _sceneEnterY2; + _pathfinderFlag = 7; + } + } else if (_savedMouseState == -6) { + if (_sceneList[curScene].exit1 != 0xFFFF) { + x = _sceneEnterX1; + y = _sceneEnterY1 - 2; + _pathfinderFlag = 14; + } + } else if (_savedMouseState == -4) { + if (_sceneList[curScene].exit3 != 0xFFFF) { + x = _sceneEnterX3; + y = 147; + _pathfinderFlag = 11; + } + } + } + + int strId = 0; + int vocH = _flags.isTalkie ? 131 : -1; + + if (_pathfinderFlag) { + if (findItem(curScene, 13) >= 0 && _savedMouseState <= -3) { + strId = 252; + } else if (_itemInHand == 72) { + strId = 257; + } else if (findItem(curScene, 72) >= 0 && _savedMouseState <= -3) { + strId = 256; + } else if (getInventoryItemSlot(72) != -1 && _savedMouseState <= -3) { + strId = 257; + } + } + + if (strId) { + updateCharFacing(); + objectChat(getTableString(strId, _cCodeBuffer, 1), 0, vocH, strId); + _pathfinderFlag = 0; + return 0; + } + + if (ABS(_mainCharacter.x1 - x) < 4 && ABS(_mainCharacter.y1 - y) < 2) { + _pathfinderFlag = 0; + return 0; + } + + int curX = _mainCharacter.x1 & ~3; + int curY = _mainCharacter.y1 & ~1; + int dstX = x & ~3; + int dstY = y & ~1; + + int wayLength = findWay(curX, curY, dstX, dstY, _movFacingTable, 600); + _pathfinderFlag = 0; + _timer->disable(5); + + if (wayLength != 0 && wayLength != 0x7D00) + refreshNPC = (trySceneChange(_movFacingTable, unk1, unk2) != 0); + + int charLayer = _screen->getLayer(_mainCharacter.x1, _mainCharacter.y1); + if (_layerFlagTable[charLayer] != 0 && !queryGameFlag(0x163)) { + if (queryGameFlag(0x164)) { + _screen->hideMouse(); + _timer->disable(5); + runAnimationScript("_ZANBURN.EMC", 0, 1, 1, 0); + _deathHandler = 7; + snd_playWanderScoreViaMap(0x53, 1); + } else { + objectChat(getTableString(0xFD, _cCodeBuffer, 1), 0, 0x83, 0xFD); + setGameFlag(0x164); + _timer->enable(5); + _timer->setCountdown(5, 120); + } + } else if (queryGameFlag(0x164)) { + objectChat(getTableString(0xFE, _cCodeBuffer, 1), 0, 0x83, 0xFE); + resetGameFlag(0x164); + _timer->disable(5); + } + + if (refreshNPC) + enterNewSceneUnk2(0); + + _pathfinderFlag = 0; + return refreshNPC; +} + +int KyraEngine_HoF::getCharacterWalkspeed() const { + return _timer->getDelay(0); +} + +void KyraEngine_HoF::updateCharAnimFrame(int *table) { + static const int unkFrame1 = 17; + static const int unkFrame2 = 10; + static const int unkFrame3 = 24; + static const int unkFrame4 = 19; + static const int unkFrame5 = 21; + static const int unkFrame6 = 31; + static const int unkFrame7 = 26; + + Character *character = &_mainCharacter; + ++character->animFrame; + + int facing = character->facing; + + if (table) { + if (table[0] != table[-1] && table[-1] == table[1]) { + facing = getOppositeFacingDirection(table[-1]); + table[0] = table[-1]; + } + } + + if (!facing) { + ++_characterFacingCountTable[0]; + } else if (facing == 4) { + ++_characterFacingCountTable[1]; + } else if (facing == 7 || facing == 1 || facing == 5 || facing == 3) { + if (facing == 7 || facing == 1) { + if (_characterFacingCountTable[0] > 2) + facing = 0; + } else { + if (_characterFacingCountTable[1] > 2) + facing = 4; + } + + _characterFacingCountTable[0] = 0; + _characterFacingCountTable[1] = 0; + } + + if (facing == 0) { + if (character->animFrame < unkFrame7) + character->animFrame = unkFrame7; + + if (character->animFrame > unkFrame6) + character->animFrame = unkFrame7; + } else if (facing == 4) { + if (character->animFrame < unkFrame4) + character->animFrame = unkFrame4; + + if (character->animFrame > unkFrame3) + character->animFrame = unkFrame4; + } else { + if (character->animFrame > unkFrame4) + character->animFrame = unkFrame5; + + if (character->animFrame == unkFrame1) + character->animFrame = unkFrame2; + + if (character->animFrame > unkFrame1) + character->animFrame = unkFrame2 + 2; + } + + updateCharacterAnim(0); +} + +bool KyraEngine_HoF::checkCharCollision(int x, int y) { + int scale1 = 0, scale2 = 0, scale3 = 0; + int x1 = 0, x2 = 0, y1 = 0, y2 = 0; + scale1 = getScale(_mainCharacter.x1, _mainCharacter.y1); + scale2 = (scale1 * 24) >> 8; + scale3 = (scale1 * 48) >> 8; + + x1 = _mainCharacter.x1 - (scale2 >> 1); + x2 = _mainCharacter.x1 + (scale2 >> 1); + y1 = _mainCharacter.y1 - scale3; + y2 = _mainCharacter.y1; + + if (x >= x1 && x <= x2 && y >= y1 && y <= y2) + return true; + + return false; +} + +int KyraEngine_HoF::initAnimationShapes(uint8 *filedata) { + const int lastEntry = MIN(_animShapeLastEntry, 31); + for (int i = 0; i < lastEntry; ++i) { + addShapeToPool(filedata, i+33, i); + ShapeDesc *desc = &_shapeDescTable[24+i]; + desc->xAdd = _animShapeXAdd; + desc->yAdd = _animShapeYAdd; + desc->width = _animShapeWidth; + desc->height = _animShapeHeight; + } + return lastEntry; +} + +void KyraEngine_HoF::uninitAnimationShapes(int count, uint8 *filedata) { + for (int i = 0; i < count; ++i) + remShapeFromPool(i+33); + delete[] filedata; + setNextIdleAnimTimer(); +} + +void KyraEngine_HoF::setNextIdleAnimTimer() { + _nextIdleAnim = _system->getMillis() + _rnd.getRandomNumberRng(10, 15) * 60 * _tickLength; +} + +void KyraEngine_HoF::showIdleAnim() { + static const uint8 scriptMinTable[] = { + 0x00, 0x05, 0x07, 0x08, 0x00, 0x09, 0x0A, 0x0B, 0xFF, 0x00 + }; + + static const uint8 scriptMaxTable[] = { + 0x04, 0x06, 0x07, 0x08, 0x04, 0x09, 0x0A, 0x0B, 0xFF, 0x00 + }; + + if (queryGameFlag(0x159) && _flags.isTalkie) + return; + + if (!_useSceneIdleAnim && _flags.isTalkie) { + _useSceneIdleAnim = true; + randomSceneChat(); + } else { + _useSceneIdleAnim = false; + if (_characterShapeFile > 8) + return; + + int scriptMin = scriptMinTable[_characterShapeFile-1]; + int scriptMax = scriptMaxTable[_characterShapeFile-1]; + int script = 0; + + if (scriptMin < scriptMax) { + do { + script = _rnd.getRandomNumberRng(scriptMin, scriptMax); + } while (script == _lastIdleScript); + } else { + script = scriptMin; + } + + runIdleScript(script); + _lastIdleScript = script; + } +} + +void KyraEngine_HoF::runIdleScript(int script) { + if (script < 0 || script >= 12) + script = 0; + + if (_mainCharacter.animFrame != 18) { + setNextIdleAnimTimer(); + } else { + // FIXME: move this to staticres.cpp? + static const char *const idleScriptFiles[] = { + "_IDLHAIR.EMC", "_IDLDUST.EMC", "_IDLLEAN.EMC", "_IDLDIRT.EMC", "_IDLTOSS.EMC", "_IDLNOSE.EMC", + "_IDLBRSH.EMC", "_Z3IDLE.EMC", "_Z4IDLE.EMC", "_Z6IDLE.EMC", "_Z7IDLE.EMC", "_Z8IDLE.EMC" + }; + + runAnimationScript(idleScriptFiles[script], 1, 1, 1, 1); + } +} + +#pragma mark - + +void KyraEngine_HoF::backUpGfxRect24x24(int x, int y) { + _screen->copyRegionToBuffer(_screen->_curPage, x, y, 24, 24, _gfxBackUpRect); +} + +void KyraEngine_HoF::restoreGfxRect24x24(int x, int y) { + _screen->copyBlockToPage(_screen->_curPage, x, y, 24, 24, _gfxBackUpRect); +} + +void KyraEngine_HoF::backUpGfxRect32x32(int x, int y) { + _screen->copyRegionToBuffer(_screen->_curPage, x, y, 32, 32, _gfxBackUpRect); +} + +void KyraEngine_HoF::restoreGfxRect32x32(int x, int y) { + _screen->copyBlockToPage(_screen->_curPage, x, y, 32, 32, _gfxBackUpRect); +} + +#pragma mark - + +void KyraEngine_HoF::openTalkFile(int newFile) { + char talkFilename[16]; + + if (_oldTalkFile > 0) { + sprintf(talkFilename, "CH%dVOC.TLK", _oldTalkFile); + _res->unloadPakFile(talkFilename); + _oldTalkFile = -1; + } + + if (newFile == 0) + strcpy(talkFilename, "ANYTALK.TLK"); + else + sprintf(talkFilename, "CH%dVOC.TLK", newFile); + + _oldTalkFile = newFile; + + if (!_res->loadPakFile(talkFilename)) { + if (speechEnabled()) { + warning("Couldn't load voice file '%s', falling back to text only mode", talkFilename); + _configVoice = 0; + + // Sync the config manager with the new settings + writeSettings(); + } + } +} + +void KyraEngine_HoF::snd_playVoiceFile(int id) { + char vocFile[9]; + assert(id >= 0 && id <= 9999999); + sprintf(vocFile, "%07d", id); + if (_sound->isVoicePresent(vocFile)) { + snd_stopVoice(); + + while (!_sound->voicePlay(vocFile, &_speechHandle)) { + updateWithText(); + _system->delayMillis(10); + } + } +} + +void KyraEngine_HoF::snd_loadSoundFile(int id) { + if (id < 0 || !_trackMap) + return; + + assert(id < _trackMapSize); + int file = _trackMap[id*2]; + _curSfxFile = _curMusicTheme = file; + _sound->loadSoundFile(file); +} + +void KyraEngine_HoF::playVoice(int high, int low) { + if (!_flags.isTalkie) + return; + int vocFile = high * 10000 + low * 10; + if (speechEnabled()) + snd_playVoiceFile(vocFile); +} + +void KyraEngine_HoF::snd_playSoundEffect(int track, int volume) { + if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) { + if (track == 10) + track = _lastSfxTrack; + + if (track == 10 || track == -1) + return; + + _lastSfxTrack = track; + } + + int16 vocIndex = (int16)READ_LE_UINT16(&_ingameSoundIndex[track * 2]); + if (vocIndex != -1) { + _sound->voicePlay(_ingameSoundList[vocIndex], 0, 255, 255, true); + } else if (_flags.platform == Common::kPlatformDOS) { + if (_sound->getSfxType() == Sound::kMidiMT32) + track = track < _mt32SfxMapSize ? _mt32SfxMap[track] - 1 : -1; + else if (_sound->getSfxType() == Sound::kMidiGM) + track = track < _gmSfxMapSize ? _gmSfxMap[track] - 1 : -1; + else if (_sound->getSfxType() == Sound::kPCSpkr) + track = track < _pcSpkSfxMapSize ? _pcSpkSfxMap[track] - 1 : -1; + + if (track != -1) + KyraEngine_v1::snd_playSoundEffect(track); + + // TODO ?? Maybe there is a way to let users select whether they want + // voc, midi or adl sfx (even though it makes no sense to choose anything but voc). + // The PC-98 version has support for non-pcm sound effects, but only for tracks + // which also have voc files. The syntax would be: + // KyraEngine_v1::snd_playSoundEffect(vocIndex); + } +} + +#pragma mark - + +void KyraEngine_HoF::loadInvWsa(const char *filename, int run_, int delayTime, int page, int sfx, int sFrame, int flags) { + int wsaFlags = 1; + if (flags) + wsaFlags |= 2; + + if (!_invWsa.wsa) + _invWsa.wsa = new WSAMovie_v2(this); + + if (!_invWsa.wsa->open(filename, wsaFlags, 0)) + error("Couldn't open inventory WSA file '%s'", filename); + + _invWsa.curFrame = 0; + _invWsa.lastFrame = _invWsa.wsa->frames(); + + _invWsa.x = _invWsa.wsa->xAdd(); + _invWsa.y = _invWsa.wsa->yAdd(); + _invWsa.w = _invWsa.wsa->width(); + _invWsa.h = _invWsa.wsa->height(); + _invWsa.x2 = _invWsa.x + _invWsa.w - 1; + _invWsa.y2 = _invWsa.y + _invWsa.h - 1; + + _invWsa.delay = delayTime; + _invWsa.page = page; + _invWsa.sfx = sfx; + + _invWsa.specialFrame = sFrame; + + if (_invWsa.page) + _screen->copyRegion(_invWsa.x, _invWsa.y, _invWsa.x, _invWsa.y, _invWsa.w, _invWsa.h, 0, _invWsa.page, Screen::CR_NO_P_CHECK); + + _invWsa.running = true; + _invWsa.timer = _system->getMillis(); + + if (run_) { + while (_invWsa.running && !skipFlag() && !shouldQuit()) { + update(); + _system->delayMillis(10); + } + + if (skipFlag()) { + resetSkipFlag(); + displayInvWsaLastFrame(); + } + } +} + +void KyraEngine_HoF::closeInvWsa() { + _invWsa.wsa->close(); + delete _invWsa.wsa; + _invWsa.wsa = 0; + _invWsa.running = false; +} + +void KyraEngine_HoF::updateInvWsa() { + if (!_invWsa.running || !_invWsa.wsa) + return; + + if (_invWsa.timer > _system->getMillis()) + return; + + _invWsa.wsa->displayFrame(_invWsa.curFrame, _invWsa.page, 0, 0, 0, 0, 0); + + if (_invWsa.page) + _screen->copyRegion(_invWsa.x, _invWsa.y, _invWsa.x, _invWsa.y, _invWsa.w, _invWsa.h, _invWsa.page, 0, Screen::CR_NO_P_CHECK); + + _invWsa.timer = _system->getMillis() + _invWsa.delay * _tickLength; + + ++_invWsa.curFrame; + if (_invWsa.curFrame >= _invWsa.lastFrame) + displayInvWsaLastFrame(); + + if (_invWsa.curFrame == _invWsa.specialFrame) + snd_playSoundEffect(_invWsa.sfx); + + if (_invWsa.sfx == -2) { + switch (_invWsa.curFrame) { + case 9: case 27: case 40: + snd_playSoundEffect(0x39); + break; + + case 18: case 34: case 44: + snd_playSoundEffect(0x33); + break; + + case 48: + snd_playSoundEffect(0x38); + break; + + default: + break; + } + } +} + +void KyraEngine_HoF::displayInvWsaLastFrame() { + if (!_invWsa.wsa) + return; + + _invWsa.wsa->displayFrame(_invWsa.lastFrame-1, _invWsa.page, 0, 0, 0, 0, 0); + + if (_invWsa.page) + _screen->copyRegion(_invWsa.x, _invWsa.y, _invWsa.x, _invWsa.y, _invWsa.w, _invWsa.h, _invWsa.page, 0, Screen::CR_NO_P_CHECK); + + closeInvWsa(); + + int32 countdown = _rnd.getRandomNumberRng(45, 80); + _timer->setCountdown(2, countdown * 60); +} + +#pragma mark - + +void KyraEngine_HoF::setCauldronState(uint8 state, bool paletteFade) { + _screen->copyPalette(2, 0); + Common::SeekableReadStream *file = _res->createReadStream("_POTIONS.PAL"); + if (!file) + error("Couldn't load cauldron palette"); + file->seek(state*18, SEEK_SET); + _screen->getPalette(2).loadVGAPalette(*file, 241, 6); + delete file; + file = 0; + + if (paletteFade) { + snd_playSoundEffect((state == 0) ? 0x6B : 0x66); + _screen->fadePalette(_screen->getPalette(2), 0x4B, &_updateFunctor); + } else { + _screen->setScreenPalette(_screen->getPalette(2)); + _screen->updateScreen(); + } + + _screen->getPalette(0).copy(_screen->getPalette(2), 241, 6); + _cauldronState = state; + _cauldronUseCount = 0; + if (state == 5) + setDlgIndex(5); +} + +void KyraEngine_HoF::clearCauldronTable() { + Common::fill(_cauldronTable, ARRAYEND(_cauldronTable), -1); +} + +void KyraEngine_HoF::addFrontCauldronTable(int item) { + for (int i = 23; i >= 0; --i) + _cauldronTable[i+1] = _cauldronTable[i]; + _cauldronTable[0] = item; +} + +void KyraEngine_HoF::cauldronItemAnim(int item) { + const int x = 282; + const int y = 135; + const int mouseDstX = (x + 7) & (~1); + const int mouseDstY = (y + 15) & (~1); + int mouseX = _mouseX & (~1); + int mouseY = _mouseY & (~1); + + while (mouseY != mouseDstY) { + if (mouseY < mouseDstY) + mouseY += 2; + else if (mouseY > mouseDstY) + mouseY -= 2; + uint32 waitEnd = _system->getMillis() + _tickLength; + setMousePos(mouseX, mouseY); + _system->updateScreen(); + delayUntil(waitEnd); + } + + while (mouseX != mouseDstX) { + if (mouseX < mouseDstX) + mouseX += 2; + else if (mouseX > mouseDstX) + mouseX -= 2; + uint32 waitEnd = _system->getMillis() + _tickLength; + setMousePos(mouseX, mouseY); + _system->updateScreen(); + delayUntil(waitEnd); + } + + if (itemIsFlask(item)) { + setHandItem(19); + delayUntil(_system->getMillis()+_tickLength*30); + setHandItem(18); + } else { + _screen->hideMouse(); + backUpGfxRect32x32(x, y); + uint8 *shape = getShapePtr(item+64); + + int curY = y; + for (int i = 0; i < 12; i += 2, curY += 2) { + restoreGfxRect32x32(x, y); + uint32 waitEnd = _system->getMillis() + _tickLength; + _screen->drawShape(0, shape, x, curY, 0, 0); + _screen->updateScreen(); + delayUntil(waitEnd); + } + + snd_playSoundEffect(0x17); + + for (int i = 16; i > 0; i -= 2, curY += 2) { + _screen->setNewShapeHeight(shape, i); + restoreGfxRect32x32(x, y); + uint32 waitEnd = _system->getMillis() + _tickLength; + _screen->drawShape(0, shape, x, curY, 0, 0); + _screen->updateScreen(); + delayUntil(waitEnd); + } + + restoreGfxRect32x32(x, y); + _screen->resetShapeHeight(shape); + removeHandItem(); + _screen->showMouse(); + } +} + +bool KyraEngine_HoF::updateCauldron() { + for (int i = 0; i < 23; ++i) { + const int16 *curStateTable = _cauldronStateTables[i]; + if (*curStateTable == -2) + continue; + + int cauldronState = i; + int16 cauldronTable[25]; + memcpy(cauldronTable, _cauldronTable, sizeof(cauldronTable)); + + while (*curStateTable != -2) { + int stateValue = *curStateTable++; + int j = 0; + for (; j < 25; ++j) { + int val = cauldronTable[j]; + + switch (val) { + case 68: + val = 70; + break; + + case 133: + case 167: + val = 119; + break; + + case 130: + case 143: + case 100: + val = 12; + break; + + case 132: + case 65: + case 69: + case 74: + val = 137; + break; + + case 157: + val = 134; + break; + + default: + break; + } + + if (val == stateValue) { + cauldronTable[j] = -1; + j = 26; + } + } + + if (j == 25) + cauldronState = -1; + } + + if (cauldronState >= 0) { + showMessage(0, 0xCF); + setCauldronState(cauldronState, true); + if (cauldronState == 7) + objectChat(getTableString(0xF2, _cCodeBuffer, 1), 0, 0x83, 0xF2); + clearCauldronTable(); + return true; + } + } + + return false; +} + +void KyraEngine_HoF::cauldronRndPaletteFade() { + showMessage(0, 0xCF); + int index = _rnd.getRandomNumberRng(0x0F, 0x16); + Common::SeekableReadStream *file = _res->createReadStream("_POTIONS.PAL"); + if (!file) + error("Couldn't load cauldron palette"); + file->seek(index*18, SEEK_SET); + _screen->getPalette(0).loadVGAPalette(*file, 241, 6); + snd_playSoundEffect(0x6A); + _screen->fadePalette(_screen->getPalette(0), 0x1E, &_updateFunctor); + file->seek(0, SEEK_SET); + _screen->getPalette(0).loadVGAPalette(*file, 241, 6); + delete file; + _screen->fadePalette(_screen->getPalette(0), 0x1E, &_updateFunctor); +} + +void KyraEngine_HoF::resetCauldronStateTable(int idx) { + for (int i = 0; i < 7; ++i) + _cauldronStateTables[idx][i] = -2; +} + +bool KyraEngine_HoF::addToCauldronStateTable(int data, int idx) { + for (int i = 0; i < 7; ++i) { + if (_cauldronStateTables[idx][i] == -2) { + _cauldronStateTables[idx][i] = data; + return true; + } + } + return false; +} + +void KyraEngine_HoF::listItemsInCauldron() { + int itemsInCauldron = 0; + for (int i = 0; i < 25; ++i) { + if (_cauldronTable[i] != -1) + ++itemsInCauldron; + else + break; + } + + if (!itemsInCauldron) { + if (!_cauldronState) + objectChat(getTableString(0xF4, _cCodeBuffer, 1), 0, 0x83, 0xF4); + else + objectChat(getTableString(0xF3, _cCodeBuffer, 1), 0, 0x83, 0xF3); + } else { + objectChat(getTableString(0xF7, _cCodeBuffer, 1), 0, 0x83, 0xF7); + + char buffer[80]; + for (int i = 0; i < itemsInCauldron-1; ++i) { + char *str = buffer; + strcpy(str, getTableString(_cauldronTable[i]+54, _cCodeBuffer, 1)); + if (_lang == 1) { + if (*str == 37) + str += 2; + } + strcpy((char *)_unkBuf500Bytes, "..."); + strcat((char *)_unkBuf500Bytes, str); + strcat((char *)_unkBuf500Bytes, "..."); + objectChat((const char *)_unkBuf500Bytes, 0, 0x83, _cauldronTable[i]+54); + } + + char *str = buffer; + strcpy(str, getTableString(_cauldronTable[itemsInCauldron-1]+54, _cCodeBuffer, 1)); + if (_lang == 1) { + if (*str == 37) + str += 2; + } + strcpy((char *)_unkBuf500Bytes, "..."); + strcat((char *)_unkBuf500Bytes, str); + strcat((char *)_unkBuf500Bytes, "."); + objectChat((const char *)_unkBuf500Bytes, 0, 0x83, _cauldronTable[itemsInCauldron-1]+54); + } +} + +#pragma mark - + +void KyraEngine_HoF::dinoRide() { + _mainCharX = _mainCharY = -1; + + setGameFlag(0x15A); + enterNewScene(41, -1, 0, 0, 0); + resetGameFlag(0x15A); + + setGameFlag(0x15B); + enterNewScene(39, -1, 0, 0, 0); + resetGameFlag(0x15B); + + setGameFlag(0x16F); + + setGameFlag(0x15C); + enterNewScene(42, -1, 0, 0, 0); + resetGameFlag(0x15C); + + setGameFlag(0x15D); + enterNewScene(39, -1, 0, 0, 0); + resetGameFlag(0x15D); + + setGameFlag(0x15E); + enterNewScene(40, -1, 0, 0, 0); + resetGameFlag(0x15E); + + _mainCharX = 262; + _mainCharY = 28; + _mainCharacter.facing = 5; + _mainCharacter.animFrame = _characterFrameTable[5]; + enterNewScene(39, 4, 0, 0, 0); + setHandItem(0x61); + _screen->showMouse(); + resetGameFlag(0x159); +} + +#pragma mark - + +void KyraEngine_HoF::playTim(const char *filename) { + TIM *tim = _tim->load(filename, &_timOpcodes); + if (!tim) + return; + + _tim->resetFinishedFlag(); + while (!shouldQuit() && !_tim->finished()) { + _tim->exec(tim, 0); + if (_chatText) + updateWithText(); + else + update(); + delay(10); + } + + _tim->unload(tim); +} + +#pragma mark - + +void KyraEngine_HoF::registerDefaultSettings() { + KyraEngine_v1::registerDefaultSettings(); + + // Most settings already have sensible defaults. This one, however, is + // specific to the Kyra engine. + ConfMan.registerDefault("walkspeed", 5); +} + +void KyraEngine_HoF::writeSettings() { + ConfMan.setInt("talkspeed", ((_configTextspeed-2) * 255) / 95); + + switch (_lang) { + case 1: + _flags.lang = Common::FR_FRA; + break; + + case 2: + _flags.lang = Common::DE_DEU; + break; + + case 3: + _flags.lang = Common::JA_JPN; + break; + + case 0: + default: + _flags.lang = Common::EN_ANY; + } + + if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG) + _flags.lang = _flags.fanLang; + + ConfMan.set("language", Common::getLanguageCode(_flags.lang)); + + KyraEngine_v1::writeSettings(); +} + +void KyraEngine_HoF::readSettings() { + KyraEngine_v2::readSettings(); + + int talkspeed = ConfMan.getInt("talkspeed"); + _configTextspeed = (talkspeed*95)/255 + 2; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/kyra_hof.h b/engines/kyra/engine/kyra_hof.h new file mode 100644 index 0000000000..588efbb5ab --- /dev/null +++ b/engines/kyra/engine/kyra_hof.h @@ -0,0 +1,697 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_KYRA_HOF_H +#define KYRA_KYRA_HOF_H + +#include "kyra/engine/kyra_v2.h" +#include "kyra/script/script.h" +#include "kyra/script/script_tim.h" +#include "kyra/graphics/screen_hof.h" +#include "kyra/text/text_hof.h" +#include "kyra/gui/gui_hof.h" + +#include "common/list.h" +#include "common/func.h" + +namespace Kyra { + +//class WSAMovie_v2; +//class KyraEngine_HoF; +class TextDisplayer_HoF; +class SeqPlayer_HOF; + +struct TIM; + +class KyraEngine_HoF : public KyraEngine_v2 { +friend class Debugger_HoF; +friend class TextDisplayer_HoF; +friend class GUI_HoF; +public: + KyraEngine_HoF(OSystem *system, const GameFlags &flags); + ~KyraEngine_HoF(); + + void pauseEngineIntern(bool pause); + + Screen *screen() { return _screen; } + Screen_v2 *screen_v2() const { return _screen; } + GUI *gui() const { return _gui; } + virtual TextDisplayer *text() { return _text; } + int language() const { return _lang; } + +protected: + static const EngineDesc _hofEngineDesc; + + // intro/outro + void seq_showStarcraftLogo(); + + int seq_playIntro(); + int seq_playOutro(); + int seq_playDemo(); + + void seq_pausePlayer(bool toggle); + + Common::Error init(); + Common::Error go(); + + Screen_HoF *_screen; + TextDisplayer_HoF *_text; + TIMInterpreter *_tim; + + static const int8 _dosTrackMap[]; + static const int _dosTrackMapSize; + static const int8 _mt32SfxMap[]; + static const int _mt32SfxMapSize; + static const int8 _gmSfxMap[]; + static const int _gmSfxMapSize; + static const int8 _pcSpkSfxMap[]; + static const int _pcSpkSfxMapSize; + +protected: + // game initialization + void startup(); + void runLoop(); + void cleanup(); + + void registerDefaultSettings(); + void writeSettings(); + void readSettings(); + uint8 _configTextspeed; + + // TODO: get rid of all variables having pointers to the static resources if possible + // i.e. let them directly use the _staticres functions + void initStaticResource(); + + void setupTimers(); + void setupOpcodeTable(); + + void loadMouseShapes(); + void loadItemShapes(); + + // run + void update(); + void updateWithText(); + + Common::Functor0Mem _updateFunctor; + + void updateMouse(); + + void dinoRide(); + + void handleInput(int x, int y); + bool handleInputUnkSub(int x, int y); + + int inputSceneChange(int x, int y, int unk1, int unk2); + + // gfx/animation specific + bool _inventorySaved; + void backUpPage0(); + void restorePage0(); + + uint8 *_gfxBackUpRect; + + void backUpGfxRect24x24(int x, int y); + void restoreGfxRect24x24(int x, int y); + void backUpGfxRect32x32(int x, int y); + void restoreGfxRect32x32(int x, int y); + + uint8 *_sceneShapeTable[50]; + + WSAMovie_v2 *_wsaSlots[10]; + + void freeSceneShapePtrs(); + + struct ShapeDesc { + uint8 unk0, unk1, unk2, unk3, unk4; + uint16 width, height; + int16 xAdd, yAdd; + }; + + ShapeDesc *_shapeDescTable; + + void loadCharacterShapes(int shapes); + void loadInventoryShapes(); + + void resetScaleTable(); + void setScaleTableItem(int item, int data); + int getScale(int x, int y); + uint16 _scaleTable[15]; + + void setDrawLayerTableEntry(int entry, int data); + int getDrawLayer(int x, int y); + int _drawLayerTable[15]; + + int _layerFlagTable[16]; // seems to indicate layers where items get destroyed when dropped to (TODO: check this!) + + int initAnimationShapes(uint8 *filedata); + void uninitAnimationShapes(int count, uint8 *filedata); + + // animator + uint8 *_gamePlayBuffer; + void restorePage3(); + + void clearAnimObjects(); + + void refreshAnimObjects(int force); + + void drawAnimObjects(); + void drawSceneAnimObject(AnimObj *obj, int x, int y, int drawLayer); + void drawCharacterAnimObject(AnimObj *obj, int x, int y, int drawLayer); + + void updateItemAnimations(); + + void updateCharFacing(); + void updateCharacterAnim(int); + void updateSceneAnim(int anim, int newFrame); + + int _animObj0Width, _animObj0Height; + void setCharacterAnimDim(int w, int h); + void resetCharacterAnimDim(); + + // scene + const char *_sceneCommentString; + uint8 _scenePal[688]; + + void enterNewScene(uint16 newScene, int facing, int unk1, int unk2, int unk3); + void enterNewSceneUnk1(int facing, int unk1, int unk2); + void enterNewSceneUnk2(int unk1); + void unloadScene(); + + void loadScenePal(); + void loadSceneMsc(); + + void fadeScenePal(int srcIndex, int delay); + + void startSceneScript(int unk1); + void runSceneScript2(); + void runSceneScript4(int unk1); + void runSceneScript7(); + + void initSceneAnims(int unk1); + void initSceneScreen(int unk1); + + int trySceneChange(int *moveTable, int unk1, int updateChar); + int checkSceneChange(); + + // pathfinder + bool lineIsPassable(int x, int y); + + // item + void setMouseCursor(Item item); + + uint8 _itemHtDat[176]; + + int checkItemCollision(int x, int y); + void updateWaterFlasks(); + + bool dropItem(int unk1, Item item, int x, int y, int unk2); + bool processItemDrop(uint16 sceneId, Item item, int x, int y, int unk1, int unk2); + void itemDropDown(int startX, int startY, int dstX, int dstY, int itemSlot, Item item); + void exchangeMouseItem(int itemPos); + bool pickUpItem(int x, int y); + + bool isDropable(int x, int y); + + static const byte _itemStringMap[]; + static const int _itemStringMapSize; + + static const Item _flaskTable[]; + bool itemIsFlask(Item item); + + // inventory + static const int _inventoryX[]; + static const int _inventoryY[]; + static const uint16 _itemMagicTable[]; + + int getInventoryItemSlot(Item item); + void removeSlotFromInventory(int slot); + bool checkInventoryItemExchange(Item item, int slot); + void drawInventoryShape(int page, Item item, int slot); + void clearInventorySlot(int slot, int page); + void redrawInventory(int page); + void scrollInventoryWheel(); + int findFreeVisibleInventorySlot(); + + ActiveItemAnim _activeItemAnim[15]; + int _nextAnimItem; + + // gui + bool _menuDirectlyToLoad; + GUI_HoF *_gui; + + void loadButtonShapes(); + void setupLangButtonShapes(); + uint8 *_buttonShapes[19]; + + void initInventoryButtonList(); + Button *_inventoryButtons; + Button *_buttonList; + + int scrollInventory(Button *button); + int buttonInventory(Button *button); + int bookButton(Button *button); + int cauldronButton(Button *button); + int cauldronClearButton(Button *button); + + // book + static const int _bookPageYOffset[]; + static const byte _bookTextColorMap[]; + + int _bookMaxPage; + int _bookNewPage; + int _bookCurPage; + int _bookBkgd; + bool _bookShown; + + void loadBookBkgd(); + void showBookPage(); + void bookLoop(); + + void bookDecodeText(uint8 *text); + void bookPrintText(int dstPage, const uint8 *text, int x, int y, uint8 color); + + int bookPrevPage(Button *button); + int bookNextPage(Button *button); + int bookClose(Button *button); + + // cauldron + uint8 _cauldronState; + int16 _cauldronUseCount; + int16 _cauldronTable[25]; + int16 _cauldronStateTables[23][7]; + + static const int16 _cauldronProtectedItems[]; + static const int16 _cauldronBowlTable[]; + static const int16 _cauldronMagicTable[]; + static const int16 _cauldronMagicTableScene77[]; + static const uint8 _cauldronStateTable[]; + + void resetCauldronStateTable(int idx); + bool addToCauldronStateTable(int data, int idx); + + void setCauldronState(uint8 state, bool paletteFade); + void clearCauldronTable(); + void addFrontCauldronTable(int item); + void cauldronItemAnim(int item); + void cauldronRndPaletteFade(); + bool updateCauldron(); + void listItemsInCauldron(); + + // localization + void loadCCodeBuffer(const char *file); + void loadOptionsBuffer(const char *file); + void loadChapterBuffer(int chapter); + uint8 *_optionsBuffer; + uint8 *_cCodeBuffer; + + uint8 *_chapterBuffer; + int _currentChapter; + int _newChapterFile; + + uint8 *getTableEntry(uint8 *buffer, int id); + char *getTableString(int id, uint8 *buffer, int decode); + const char *getChapterString(int id); + + void changeFileExtension(char *buffer); + + // - Just used in French version + int getItemCommandStringDrop(Item item); + int getItemCommandStringPickUp(Item item); + int getItemCommandStringInv(Item item); + // - + + char _internStringBuf[200]; + static const char *const _languageExtension[]; + static const char *const _scriptLangExt[]; + + // character + bool _useCharPal; + bool _setCharPalFinal; + int _charPalEntry; + uint8 _charPalTable[16]; + void updateCharPal(int unk1); + void setCharPalEntry(int entry, int value); + + int _characterFacingCountTable[2]; + + int getCharacterWalkspeed() const; + void updateCharAnimFrame(int *table); + + bool checkCharCollision(int x, int y); + + static const uint8 _characterFrameTable[]; + + // text + void showMessageFromCCode(int id, int16 palIndex, int); + void showMessage(const char *string, int16 palIndex); + void showChapterMessage(int id, int16 palIndex); + + void updateCommandLineEx(int str1, int str2, int16 palIndex); + + const char *_shownMessage; + + byte _messagePal[3]; + bool _fadeMessagePalette; + void fadeMessagePalette(); + + // chat + bool _chatIsNote; + + int chatGetType(const char *text); + int chatCalcDuration(const char *text); + + void objectChat(const char *text, int object, int vocHigh = -1, int vocLow = -1); + void objectChatInit(const char *text, int object, int vocHigh = -1, int vocLow = -1); + void objectChatPrintText(const char *text, int object); + void objectChatProcess(const char *script); + void objectChatWaitToFinish(); + + void startDialogue(int dlgIndex); + + void zanthSceneStartupChat(); + void randomSceneChat(); + void updateDlgBuffer(); + void loadDlgHeader(int &csEntry, int &vocH, int &scIndex1, int &scIndex2); + void processDialogue(int dlgOffset, int vocH = 0, int csEntry = 0); + void npcChatSequence(const char *str, int objectId, int vocHigh = -1, int vocLow = -1); + void setDlgIndex(int dlgIndex); + + int _npcTalkChpIndex; + int _npcTalkDlgIndex; + uint8 _newSceneDlgState[32]; + int8 **_conversationState; + uint8 *_dlgBuffer; + + // Talk object handling + void initTalkObject(int index); + void deinitTalkObject(int index); + + struct TalkObject { + char filename[13]; + int8 scriptId; + int16 x, y; + int8 color; + }; + TalkObject *_talkObjectList; + + struct TalkSections { + TIM *STATim; + TIM *TLKTim; + TIM *ENDTim; + }; + TalkSections _currentTalkSections; + + char _TLKFilename[13]; + + // tim + void playTim(const char *filename); + + int t2_initChat(const TIM *tim, const uint16 *param); + int t2_updateSceneAnim(const TIM *tim, const uint16 *param); + int t2_resetChat(const TIM *tim, const uint16 *param); + int t2_playSoundEffect(const TIM *tim, const uint16 *param); + + Common::Array _timOpcodes; + + // sound + int _oldTalkFile; + int _currentTalkFile; + void openTalkFile(int newFile); + int _lastSfxTrack; + + virtual void snd_playVoiceFile(int id); + void snd_loadSoundFile(int id); + + void playVoice(int high, int low); + void snd_playSoundEffect(int track, int volume=0xFF); + + // timer + void timerFadeOutMessage(int); + void timerCauldronAnimation(int); + void timerFunc4(int); + void timerFunc5(int); + void timerBurnZanthia(int); + + void setTimer1DelaySecs(int secs); + + uint32 _nextIdleAnim; + int _lastIdleScript; + bool _useSceneIdleAnim; + + void setNextIdleAnimTimer(); + void showIdleAnim(); + void runIdleScript(int script); + + void setWalkspeed(uint8 speed); + + // ingame static sequence handling + void seq_makeBookOrCauldronAppear(int type); + void seq_makeBookAppear(); + + struct InventoryWsa { + int x, y, x2, y2, w, h; + int page; + int curFrame, lastFrame, specialFrame; + int sfx; + int delay; + bool running; + uint32 timer; + WSAMovie_v2 *wsa; + } _invWsa; + + // TODO: move inside KyraEngine_HoF::InventoryWsa? + void loadInvWsa(const char *filename, int run, int delay, int page, int sfx, int sFrame, int flags); + void closeInvWsa(); + + void updateInvWsa(); + void displayInvWsaLastFrame(); + + // opcodes + int o2_setCharacterFacingRefresh(EMCState *script); + int o2_setCharacterPos(EMCState *script); + int o2_defineObject(EMCState *script); + int o2_refreshCharacter(EMCState *script); + int o2_setSceneComment(EMCState *script); + int o2_setCharacterAnimFrame(EMCState *script); + int o2_setCharacterFacing(EMCState *script); + int o2_customCharacterChat(EMCState *script); + int o2_soundFadeOut(EMCState *script); + int o2_showChapterMessage(EMCState *script); + int o2_restoreTalkTextMessageBkgd(EMCState *script); + int o2_wsaClose(EMCState *script); + int o2_meanWhileScene(EMCState *script); + int o2_backUpScreen(EMCState *script); + int o2_restoreScreen(EMCState *script); + int o2_displayWsaFrame(EMCState *script); + int o2_displayWsaSequentialFramesLooping(EMCState *script); + int o2_wsaOpen(EMCState *script); + int o2_displayWsaSequentialFrames(EMCState *script); + int o2_displayWsaSequence(EMCState *script); + int o2_addItemToInventory(EMCState *script); + int o2_drawShape(EMCState *script); + int o2_addItemToCurScene(EMCState *script); + int o2_loadSoundFile(EMCState *script); + int o2_removeSlotFromInventory(EMCState *script); + int o2_removeItemFromInventory(EMCState *script); + int o2_countItemInInventory(EMCState *script); + int o2_countItemsInScene(EMCState *script); + int o2_wipeDownMouseItem(EMCState *script); + int o2_getElapsedSecs(EMCState *script); + int o2_getTimerDelay(EMCState *script); + int o2_delaySecs(EMCState *script); + int o2_setTimerDelay(EMCState *script); + int o2_setScaleTableItem(EMCState *script); + int o2_setDrawLayerTableItem(EMCState *script); + int o2_setCharPalEntry(EMCState *script); + int o2_loadZShapes(EMCState *script); + int o2_drawSceneShape(EMCState *script); + int o2_drawSceneShapeOnPage(EMCState *script); + int o2_disableAnimObject(EMCState *script); + int o2_enableAnimObject(EMCState *script); + int o2_loadPalette384(EMCState *script); + int o2_setPalette384(EMCState *script); + int o2_restoreBackBuffer(EMCState *script); + int o2_backUpInventoryGfx(EMCState *script); + int o2_disableSceneAnim(EMCState *script); + int o2_enableSceneAnim(EMCState *script); + int o2_restoreInventoryGfx(EMCState *script); + int o2_setSceneAnimPos2(EMCState *script); + int o2_fadeScenePal(EMCState *script); + int o2_enterNewScene(EMCState *script); + int o2_switchScene(EMCState *script); + int o2_setPathfinderFlag(EMCState *script); + int o2_getSceneExitToFacing(EMCState *script); + int o2_setLayerFlag(EMCState *script); + int o2_setZanthiaPos(EMCState *script); + int o2_loadMusicTrack(EMCState *script); + int o2_setSceneAnimPos(EMCState *script); + int o2_setCauldronState(EMCState *script); + int o2_showItemString(EMCState *script); + int o2_isAnySoundPlaying(EMCState *script); + int o2_setDrawNoShapeFlag(EMCState *script); + int o2_setRunFlag(EMCState *script); + int o2_showLetter(EMCState *script); + int o2_playFireflyScore(EMCState *script); + int o2_encodeShape(EMCState *script); + int o2_defineSceneAnim(EMCState *script); + int o2_updateSceneAnim(EMCState *script); + int o2_addToSceneAnimPosAndUpdate(EMCState *script); + int o2_useItemOnMainChar(EMCState *script); + int o2_startDialogue(EMCState *script); + int o2_addCauldronStateTableEntry(EMCState *script); + int o2_setCountDown(EMCState *script); + int o2_getCountDown(EMCState *script); + int o2_pressColorKey(EMCState *script); + int o2_objectChat(EMCState *script); + int o2_changeChapter(EMCState *script); + int o2_getColorCodeFlag1(EMCState *script); + int o2_setColorCodeFlag1(EMCState *script); + int o2_getColorCodeFlag2(EMCState *script); + int o2_setColorCodeFlag2(EMCState *script); + int o2_getColorCodeValue(EMCState *script); + int o2_setColorCodeValue(EMCState *script); + int o2_countItemInstances(EMCState *script); + int o2_removeItemFromScene(EMCState *script); + int o2_initObject(EMCState *script); + int o2_npcChat(EMCState *script); + int o2_deinitObject(EMCState *script); + int o2_playTimSequence(EMCState *script); + int o2_makeBookOrCauldronAppear(EMCState *script); + int o2_resetInputColorCode(EMCState *script); + int o2_mushroomEffect(EMCState *script); + int o2_customChat(EMCState *script); + int o2_customChatFinish(EMCState *script); + int o2_setupSceneAnimation(EMCState *script); + int o2_stopSceneAnimation(EMCState *script); + int o2_processPaletteIndex(EMCState *script); + int o2_updateTwoSceneAnims(EMCState *script); + int o2_getRainbowRoomData(EMCState *script); + int o2_drawSceneShapeEx(EMCState *script); + int o2_midiSoundFadeout(EMCState *script); + int o2_getSfxDriver(EMCState *script); + int o2_getVocSupport(EMCState *script); + int o2_getMusicDriver(EMCState *script); + int o2_zanthiaChat(EMCState *script); + int o2_isVoiceEnabled(EMCState *script); + int o2_isVoicePlaying(EMCState *script); + int o2_stopVoicePlaying(EMCState *script); + int o2_getGameLanguage(EMCState *script); + int o2_demoFinale(EMCState *script); + int o2_dummy(EMCState *script); + + // animation opcodes + int o2a_setCharacterFrame(EMCState *script); + + // script + void runStartScript(int script, int unk1); + void loadNPCScript(); + + bool _noScriptEnter; + + EMCData _npcScriptData; + + // pathfinder + uint8 *_unkBuf500Bytes; + uint8 *_unkBuf200kByte; + bool _chatAltFlag; + + // sequence player +/* ActiveWSA *_activeWSA; + ActiveText *_activeText; + */ + /*const char *const *_sequencePakList; + int _sequencePakListSize;*/ + const char *const *_ingamePakList; + int _ingamePakListSize; + + const char *const *_musicFileListIntro; + int _musicFileListIntroSize; + const char *const *_musicFileListFinale; + int _musicFileListFinaleSize; + const char *const *_musicFileListIngame; + int _musicFileListIngameSize; + const uint8 *_cdaTrackTableIntro; + int _cdaTrackTableIntroSize; + const uint8 *_cdaTrackTableIngame; + int _cdaTrackTableIngameSize; + const uint8 *_cdaTrackTableFinale; + int _cdaTrackTableFinaleSize; + const char *const *_ingameSoundList; + int _ingameSoundListSize; + const uint16 *_ingameSoundIndex; + int _ingameSoundIndexSize; + const uint16 *_ingameTalkObjIndex; + int _ingameTalkObjIndexSize; + const char *const *_ingameTimJpStr; + int _ingameTimJpStrSize; + + const ItemAnimDefinition *_itemAnimDefinition; + int _itemAnimDefinitionSize; + + /*const HofSeqData *_sequences; + + const ItemAnimData_v1 *_demoAnimData; + int _demoAnimSize; + + int _sequenceStringsDuration[33];*/ + +/* static const uint8 _seqTextColorPresets[]; + char *_seqProcessedString; + WSAMovie_v2 *_seqWsa; + + bool _abortIntroFlag; + int _menuChoice;*/ + + /*uint32 _seqFrameDelay; + uint32 _seqStartTime; + uint32 _seqSubFrameStartTime; + uint32 _seqEndTime; + uint32 _seqSubFrameEndTimeInternal; + uint32 _seqWsaChatTimeout; + uint32 _seqWsaChatFrameTimeout; + + int _seqFrameCounter; + int _seqScrollTextCounter; + int _seqWsaCurrentFrame; + bool _seqSpecialFlag; + bool _seqSubframePlaying; + uint8 _seqTextColor[2]; + uint8 _seqTextColorMap[16];*/ + + static const uint8 _rainbowRoomData[]; + + // color code related vars + int _colorCodeFlag1; + int _colorCodeFlag2; + uint8 _presetColorCode[7]; + uint8 _inputColorCode[7]; + uint32 _scriptCountDown; + int _dbgPass; + + // save/load specific + Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail); + Common::Error loadGameState(int slot); +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/kyra_lok.cpp b/engines/kyra/engine/kyra_lok.cpp new file mode 100644 index 0000000000..30a83b2440 --- /dev/null +++ b/engines/kyra/engine/kyra_lok.cpp @@ -0,0 +1,990 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_lok.h" +#include "kyra/resource/resource.h" +#include "kyra/sequence/seqplayer.h" +#include "kyra/engine/sprites.h" +#include "kyra/graphics/animator_lok.h" +#include "kyra/gui/debugger.h" +#include "kyra/engine/timer.h" +#include "kyra/sound/sound.h" + +#include "common/system.h" +#include "common/config-manager.h" +#include "common/debug-channels.h" + +namespace Kyra { + +KyraEngine_LoK::KyraEngine_LoK(OSystem *system, const GameFlags &flags) + : KyraEngine_v1(system, flags) { + + _seq_Forest = _seq_KallakWriting = _seq_KyrandiaLogo = _seq_KallakMalcolm = 0; + _seq_MalcolmTree = _seq_WestwoodLogo = _seq_Demo1 = _seq_Demo2 = _seq_Demo3 = 0; + _seq_Demo4 = 0; + + _seq_WSATable = _seq_CPSTable = _seq_COLTable = _seq_textsTable = 0; + _seq_WSATable_Size = _seq_CPSTable_Size = _seq_COLTable_Size = _seq_textsTable_Size = 0; + + _roomFilenameTable = _characterImageTable = 0; + _roomFilenameTableSize = _characterImageTableSize = 0; + _itemList = _takenList = _placedList = _droppedList = _noDropList = 0; + _itemList_Size = _takenList_Size = _placedList_Size = _droppedList_Size = _noDropList_Size = 0; + _putDownFirst = _waitForAmulet = _blackJewel = _poisonGone = _healingTip = 0; + _putDownFirst_Size = _waitForAmulet_Size = _blackJewel_Size = _poisonGone_Size = _healingTip_Size = 0; + _thePoison = _fluteString = _wispJewelStrings = _magicJewelString = _flaskFull = _fullFlask = 0; + _thePoison_Size = _fluteString_Size = _wispJewelStrings_Size = 0; + _magicJewelString_Size = _flaskFull_Size = _fullFlask_Size = 0; + + _defaultShapeTable = 0; + _healingShapeTable = _healingShape2Table = 0; + _defaultShapeTableSize = _healingShapeTableSize = _healingShape2TableSize = 0; + _posionDeathShapeTable = _fluteAnimShapeTable = 0; + _posionDeathShapeTableSize = _fluteAnimShapeTableSize = 0; + _winterScrollTable = _winterScroll1Table = _winterScroll2Table = 0; + _winterScrollTableSize = _winterScroll1TableSize = _winterScroll2TableSize = 0; + _drinkAnimationTable = _brandonToWispTable = _magicAnimationTable = _brandonStoneTable = 0; + _drinkAnimationTableSize = _brandonToWispTableSize = _magicAnimationTableSize = _brandonStoneTableSize = 0; + _specialPalettes = 0; + _sprites = 0; + _animator = 0; + _seq = 0; + _characterList = 0; + _roomTable = 0; + _movFacingTable = 0; + _buttonData = 0; + _buttonDataListPtr = 0; + memset(_shapes, 0, sizeof(_shapes)); + memset(_movieObjects, 0, sizeof(_movieObjects)); + _finalA = _finalB = _finalC = 0; + _endSequenceBackUpRect = 0; + memset(_panPagesTable, 0, sizeof(_panPagesTable)); + memset(_sceneAnimTable, 0, sizeof(_sceneAnimTable)); + _currHeadShape = 0; + _currentHeadFrameTableIndex = 0; + _speechPlayTime = 0; + _seqPlayerFlag = false; + + memset(&_characterFacingZeroCount, 0, sizeof(_characterFacingZeroCount)); + memset(&_characterFacingFourCount, 0, sizeof(_characterFacingFourCount)); + + memset(&_itemBkgBackUp, 0, sizeof(_itemBkgBackUp)); + + _beadStateTimer1 = _beadStateTimer2 = 0; + memset(&_beadState1, 0, sizeof(_beadState1)); + _beadState1.x = -1; + memset(&_beadState2, 0, sizeof(_beadState2)); + + _malcolmFrame = 0; + _malcolmTimer1 = _malcolmTimer2 = 0; +} + +KyraEngine_LoK::~KyraEngine_LoK() { + for (int i = 0; i < ARRAYSIZE(_movieObjects); ++i) { + if (_movieObjects[i]) + _movieObjects[i]->close(); + delete _movieObjects[i]; + _movieObjects[i] = 0; + } + + closeFinalWsa(); + if (_emc) { + _emc->unload(&_npcScriptData); + _emc->unload(&_scriptClickData); + } + + DebugMan.clearAllDebugChannels(); + + delete _screen; + delete _sprites; + delete _animator; + delete _seq; + + delete[] _characterList; + + delete[] _roomTable; + + delete[] _movFacingTable; + + delete[] _defaultShapeTable; + + delete[] _specialPalettes; + + delete[] _gui->_scrollUpButton.data0ShapePtr; + delete[] _gui->_scrollUpButton.data1ShapePtr; + delete[] _gui->_scrollUpButton.data2ShapePtr; + delete[] _gui->_scrollDownButton.data0ShapePtr; + delete[] _gui->_scrollDownButton.data1ShapePtr; + delete[] _gui->_scrollDownButton.data2ShapePtr; + + delete[] _buttonData; + delete[] _buttonDataListPtr; + + delete _gui; + + delete[] _itemBkgBackUp[0]; + delete[] _itemBkgBackUp[1]; + + for (int i = 0; i < ARRAYSIZE(_shapes); ++i) { + if (_shapes[i] != 0) { + delete[] _shapes[i]; + for (int i2 = 0; i2 < ARRAYSIZE(_shapes); i2++) { + if (_shapes[i2] == _shapes[i] && i2 != i) { + _shapes[i2] = 0; + } + } + _shapes[i] = 0; + } + } + + for (int i = 0; i < ARRAYSIZE(_sceneAnimTable); ++i) + delete[] _sceneAnimTable[i]; +} + +Common::Error KyraEngine_LoK::init() { + if (Common::parseRenderMode(ConfMan.get("render_mode")) == Common::kRenderPC9801) + _screen = new Screen_LoK_16(this, _system); + else + _screen = new Screen_LoK(this, _system); + assert(_screen); + _screen->setResolution(); + + _debugger = new Debugger_LoK(this); + assert(_debugger); + + KyraEngine_v1::init(); + + _sprites = new Sprites(this, _system); + assert(_sprites); + _seq = new SeqPlayer(this, _system); + assert(_seq); + _animator = new Animator_LoK(this, _system); + assert(_animator); + _animator->init(5, 11, 12); + assert(*_animator); + _text = new TextDisplayer(this, screen()); + assert(_text); + _gui = new GUI_LoK(this, _screen); + assert(_gui); + + initStaticResource(); + + _sound->selectAudioResourceSet(kMusicIntro); + + if (_flags.platform == Common::kPlatformAmiga) { + _trackMap = _amigaTrackMap; + _trackMapSize = _amigaTrackMapSize; + } else { + _trackMap = _dosTrackMap; + _trackMapSize = _dosTrackMapSize; + } + + if (!_sound->init()) + error("Couldn't init sound"); + + _sound->loadSoundFile(0); + + setupButtonData(); + + _paletteChanged = 1; + _currentCharacter = 0; + _characterList = new Character[11]; + assert(_characterList); + memset(_characterList, 0, sizeof(Character) * 11); + + for (int i = 0; i < 11; ++i) + memset(_characterList[i].inventoryItems, 0xFF, sizeof(_characterList[i].inventoryItems)); + + _characterList[0].sceneId = 5; + _characterList[0].height = 48; + _characterList[0].facing = 3; + _characterList[0].currentAnimFrame = 7; + + memset(&_npcScriptData, 0, sizeof(EMCData)); + memset(&_scriptClickData, 0, sizeof(EMCData)); + + memset(&_npcScript, 0, sizeof(EMCState)); + memset(&_scriptMain, 0, sizeof(EMCState)); + memset(&_scriptClick, 0, sizeof(EMCState)); + + memset(_shapes, 0, sizeof(_shapes)); + + for (int i = 0; i < ARRAYSIZE(_movieObjects); ++i) + _movieObjects[i] = createWSAMovie(); + + memset(_flagsTable, 0, sizeof(_flagsTable)); + + _talkingCharNum = -1; + _charSayUnk3 = -1; + _disabledTalkAnimObject = _enabledTalkAnimObject = 0; + memset(_currSentenceColor, 0, 3); + _startSentencePalIndex = -1; + _fadeText = false; + + _cauldronState = 0; + _crystalState[0] = _crystalState[1] = -1; + + _brandonStatusBit = 0; + _brandonStatusBit0x02Flag = _brandonStatusBit0x20Flag = 10; + _brandonPosX = _brandonPosY = -1; + _poisonDeathCounter = 0; + + memset(_itemHtDat, 0, sizeof(_itemHtDat)); + memset(_exitList, 0xFF, sizeof(_exitList)); + _exitListPtr = 0; + _pathfinderFlag = _pathfinderFlag2 = 0; + _lastFindWayRet = 0; + _sceneChangeState = _loopFlag2 = 0; + + _movFacingTable = new int[150]; + assert(_movFacingTable); + _movFacingTable[0] = 8; + + _marbleVaseItem = -1; + memset(_foyerItemTable, -1, sizeof(_foyerItemTable)); + _itemInHand = kItemNone; + + _currentRoom = 0xFFFF; + _scenePhasingFlag = 0; + _lastProcessedItem = 0; + _lastProcessedItemHeight = 16; + + _unkScreenVar1 = 1; + _unkScreenVar2 = 0; + _unkScreenVar3 = 0; + _unkAmuletVar = 0; + + _endSequenceNeedLoading = 1; + _malcolmFlag = 0; + _beadStateVar = 0; + _endSequenceSkipFlag = 0; + _unkEndSeqVar2 = 0; + _endSequenceBackUpRect = 0; + _unkEndSeqVar4 = 0; + _unkEndSeqVar5 = 0; + _lastDisplayedPanPage = 0; + memset(_panPagesTable, 0, sizeof(_panPagesTable)); + _finalA = _finalB = _finalC = 0; + memset(&_kyragemFadingState, 0, sizeof(_kyragemFadingState)); + _kyragemFadingState.gOffset = 0x13; + _kyragemFadingState.bOffset = 0x13; + + _menuDirectlyToLoad = false; + + _lastMusicCommand = 0; + + return Common::kNoError; +} + +Common::Error KyraEngine_LoK::go() { + if (_res->getFileSize("6.FNT")) + _screen->loadFont(Screen::FID_6_FNT, "6.FNT"); + _screen->loadFont(Screen::FID_8_FNT, "8FAT.FNT"); + + _screen->setFont(_flags.lang == Common::JA_JPN ? Screen::FID_SJIS_FNT : Screen::FID_8_FNT); + + _screen->setScreenDim(0); + + _abortIntroFlag = false; + + if (_flags.isDemo && !_flags.isTalkie) { + _seqPlayerFlag = true; + seq_demo(); + _seqPlayerFlag = false; + } else { + setGameFlag(0xF3); + setGameFlag(0xFD); + if (_gameToLoad == -1) { + setGameFlag(0xEF); + _seqPlayerFlag = true; + seq_intro(); + _seqPlayerFlag = false; + + if (_flags.isDemo) { + _screen->fadeToBlack(); + return Common::kNoError; + } + + if (shouldQuit()) + return Common::kNoError; + + if (_skipIntroFlag && _abortIntroFlag && saveFileLoadable(0)) + resetGameFlag(0xEF); + } + _eventList.clear(); + startup(); + resetGameFlag(0xEF); + mainLoop(); + } + return Common::kNoError; +} + + +void KyraEngine_LoK::startup() { + static const uint8 colorMap[] = { 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0 }; + _screen->setTextColorMap(colorMap); + + _sound->selectAudioResourceSet(kMusicIngame); + if (_flags.platform == Common::kPlatformPC98) + _sound->loadSoundFile("SE.DAT"); + else + _sound->loadSoundFile(0); + +// _screen->setFont(Screen::FID_6_FNT); + _screen->setAnimBlockPtr(3750); + memset(_sceneAnimTable, 0, sizeof(_sceneAnimTable)); + loadMouseShapes(); + _currentCharacter = &_characterList[0]; + for (int i = 1; i < 5; ++i) + _animator->setCharacterDefaultFrame(i); + for (int i = 5; i <= 10; ++i) + setCharactersPositions(i); + _animator->setCharactersHeight(); + resetBrandonPoisonFlags(); + _screen->_curPage = 0; + // XXX + for (int i = 0; i < 12; ++i) { + int size = _screen->getRectSize(3, 24); + _shapes[361 + i] = new byte[size]; + } + + _itemBkgBackUp[0] = new uint8[_screen->getRectSize(3, 24)]; + memset(_itemBkgBackUp[0], 0, _screen->getRectSize(3, 24)); + _itemBkgBackUp[1] = new uint8[_screen->getRectSize(4, 32)]; + memset(_itemBkgBackUp[1], 0, _screen->getRectSize(4, 32)); + + for (int i = 0; i < _roomTableSize; ++i) { + for (int item = 0; item < 12; ++item) { + _roomTable[i].itemsTable[item] = kItemNone; + _roomTable[i].itemsXPos[item] = 0xFFFF; + _roomTable[i].itemsYPos[item] = 0xFF; + _roomTable[i].needInit[item] = 0; + } + } + + loadCharacterShapes(); + loadSpecialEffectShapes(); + loadItems(); + loadButtonShapes(); + initMainButtonList(); + loadMainScreen(); + _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); + + if (_flags.platform == Common::kPlatformAmiga) + _screen->loadPaletteTable("PALETTE.DAT", 6); + + // XXX + _animator->initAnimStateList(); + setCharactersInDefaultScene(); + + if (!_emc->load("_STARTUP.EMC", &_npcScriptData, &_opcodes)) + error("Could not load \"_STARTUP.EMC\" script"); + _emc->init(&_scriptMain, &_npcScriptData); + + if (!_emc->start(&_scriptMain, 0)) + error("Could not start script function 0 of script \"_STARTUP.EMC\""); + + while (_emc->isValid(&_scriptMain)) + _emc->run(&_scriptMain); + + _emc->unload(&_npcScriptData); + + if (!_emc->load("_NPC.EMC", &_npcScriptData, &_opcodes)) + error("Could not load \"_NPC.EMC\" script"); + + snd_playTheme(1, -1); + if (_gameToLoad == -1) { + enterNewScene(_currentCharacter->sceneId, _currentCharacter->facing, 0, 0, 1); + if (_abortIntroFlag && _skipIntroFlag && saveFileLoadable(0)) { + _menuDirectlyToLoad = true; + _screen->setMouseCursor(1, 1, _shapes[0]); + _screen->showMouse(); + _gui->buttonMenuCallback(0); + _menuDirectlyToLoad = false; + } else if (!shouldQuit()) { + saveGameStateIntern(0, "New game", 0); + } + } else { + _screen->setFont(_flags.lang == Common::JA_JPN ? Screen::FID_SJIS_FNT : Screen::FID_8_FNT); + loadGameStateCheck(_gameToLoad); + _gameToLoad = -1; + } +} + +void KyraEngine_LoK::mainLoop() { + // Initialize debugger since how it should be fully usable + _debugger->initialize(); + + _eventList.clear(); + + while (!shouldQuit()) { + int32 frameTime = (int32)_system->getMillis(); + + if (_currentCharacter->sceneId == 210) { + updateKyragemFading(); + if (seq_playEnd() && _deathHandler != 8) + break; + } + + if (_deathHandler != -1) { + snd_playWanderScoreViaMap(0, 1); + snd_playSoundEffect(49); + _screen->setMouseCursor(1, 1, _shapes[0]); + removeHandItem(); + _gui->buttonMenuCallback(0); + _deathHandler = -1; + } + + if ((_brandonStatusBit & 2) && _brandonStatusBit0x02Flag) + _animator->animRefreshNPC(0); + + if ((_brandonStatusBit & 0x20) && _brandonStatusBit0x20Flag) { + _animator->animRefreshNPC(0); + _brandonStatusBit0x20Flag = 0; + } + + // FIXME: Why is this here? + _screen->showMouse(); + + int inputFlag = checkInput(_buttonList, _currentCharacter->sceneId != 210); + removeInputTop(); + + updateMousePointer(); + _timer->update(); + _sound->process(); + updateTextFade(); + + if (inputFlag == 198 || inputFlag == 199) + processInput(_mouseX, _mouseY); + + if (skipFlag()) + resetSkipFlag(); + + delay((frameTime + _gameSpeed) - _system->getMillis(), true, true); + } +} + +void KyraEngine_LoK::delayUntil(uint32 timestamp, bool updateTimers, bool update, bool isMainLoop) { + while (_system->getMillis() < timestamp && !shouldQuit() && !skipFlag()) { + if (updateTimers) + _timer->update(); + + if (timestamp - _system->getMillis() >= 10) + delay(10, update, isMainLoop); + } +} + +void KyraEngine_LoK::delay(uint32 amount, bool update, bool isMainLoop) { + uint32 start = _system->getMillis(); + do { + if (update) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + updateTextFade(); + updateMousePointer(); + } else { + // We need to do Screen::updateScreen here, since client code + // relies on this method to copy screen changes to the actual + // screen since at least 0af418e7ea3a41f93fcc551a45ee5bae822d812a. + _screen->updateScreen(); + } + + _isSaveAllowed = isMainLoop; + updateInput(); + _isSaveAllowed = false; + + if (_currentCharacter && _currentCharacter->sceneId == 210 && update) + updateKyragemFading(); + + if (amount > 0 && !skipFlag() && !shouldQuit()) + _system->delayMillis(10); + + // FIXME: Major hackery to allow skipping the intro + if (_seqPlayerFlag) { + for (Common::List::iterator i = _eventList.begin(); i != _eventList.end(); ++i) { + if (i->causedSkip) { + if (i->event.type == Common::EVENT_KEYDOWN && i->event.kbd.keycode == Common::KEYCODE_ESCAPE) + _abortIntroFlag = true; + else + i->causedSkip = false; + } + } + } + + if (skipFlag()) + snd_stopVoice(); + } while (!skipFlag() && _system->getMillis() < start + amount && !shouldQuit()); +} + +bool KyraEngine_LoK::skipFlag() const { + return KyraEngine_v1::skipFlag() || shouldQuit(); +} + +void KyraEngine_LoK::resetSkipFlag(bool removeEvent) { + if (removeEvent) { + _eventList.clear(); + } else { + KyraEngine_v1::resetSkipFlag(false); + } +} + +void KyraEngine_LoK::delayWithTicks(int ticks) { + uint32 nextTime = _system->getMillis() + ticks * _tickLength; + + while (_system->getMillis() < nextTime) { + _sprites->updateSceneAnims(); + _animator->updateAllObjectShapes(); + + if (_currentCharacter->sceneId == 210) { + updateKyragemFading(); + seq_playEnd(); + } + + if (skipFlag()) + break; + + if (nextTime - _system->getMillis() >= 10) + delay(10); + } +} + +#pragma mark - +#pragma mark - Animation/shape specific code +#pragma mark - + +void KyraEngine_LoK::setupShapes123(const Shape *shapeTable, int endShape, int flags) { + for (int i = 123; i <= 172; ++i) + _shapes[i] = 0; + + uint8 curImage = 0xFF; + int curPageBackUp = _screen->_curPage; + _screen->_curPage = 8; // we are using page 8 here in the original page 2 was backuped and then used for this stuff + int shapeFlags = 2; + if (flags) + shapeFlags = 3; + for (int i = 123; i < 123 + endShape; ++i) { + uint8 newImage = shapeTable[i - 123].imageIndex; + if (newImage != curImage && newImage != 0xFF) { + assert(_characterImageTable); + _screen->loadBitmap(_characterImageTable[newImage], 8, 8, 0); + curImage = newImage; + } + _shapes[i] = _screen->encodeShape(shapeTable[i - 123].x << 3, shapeTable[i - 123].y, shapeTable[i - 123].w << 3, shapeTable[i - 123].h, shapeFlags); + assert(i - 7 < _defaultShapeTableSize); + _defaultShapeTable[i - 7].xOffset = shapeTable[i - 123].xOffset; + _defaultShapeTable[i - 7].yOffset = shapeTable[i - 123].yOffset; + _defaultShapeTable[i - 7].w = shapeTable[i - 123].w; + _defaultShapeTable[i - 7].h = shapeTable[i - 123].h; + } + _screen->_curPage = curPageBackUp; +} + +void KyraEngine_LoK::freeShapes123() { + for (int i = 123; i <= 172; ++i) { + delete[] _shapes[i]; + _shapes[i] = 0; + } +} + +#pragma mark - +#pragma mark - Misc stuff +#pragma mark - + +Movie *KyraEngine_LoK::createWSAMovie() { + if (_flags.platform == Common::kPlatformAmiga) + return new WSAMovieAmiga(this); + + return new WSAMovie_v1(this); +} + +void KyraEngine_LoK::setBrandonPoisonFlags(int reset) { + _brandonStatusBit |= 1; + + if (reset) + _poisonDeathCounter = 0; + + for (int i = 0; i < 0x100; ++i) + _brandonPoisonFlagsGFX[i] = i; + + _brandonPoisonFlagsGFX[0x99] = 0x34; + _brandonPoisonFlagsGFX[0x9A] = 0x35; + _brandonPoisonFlagsGFX[0x9B] = 0x37; + _brandonPoisonFlagsGFX[0x9C] = 0x38; + _brandonPoisonFlagsGFX[0x9D] = 0x2B; +} + +void KyraEngine_LoK::resetBrandonPoisonFlags() { + _brandonStatusBit = 0; + + for (int i = 0; i < 0x100; ++i) + _brandonPoisonFlagsGFX[i] = i; +} + +#pragma mark - +#pragma mark - Input +#pragma mark - + +void KyraEngine_LoK::processInput(int xpos, int ypos) { + if (processInputHelper(xpos, ypos)) + return; + + uint8 item = findItemAtPos(xpos, ypos); + if (item == 0xFF) { + _changedScene = false; + int handled = clickEventHandler(xpos, ypos); + if (_changedScene || handled) + return; + } + + // XXX _deathHandler specific + if (ypos <= 158) { + uint16 exit = 0xFFFF; + + if (xpos < 12) + exit = _walkBlockWest; + else if (xpos >= 308) + exit = _walkBlockEast; + else if (ypos >= 136) + exit = _walkBlockSouth; + else if (ypos < 12) + exit = _walkBlockNorth; + + if (exit != 0xFFFF) { + handleSceneChange(xpos, ypos, 1, 1); + return; + } else { + int script = checkForNPCScriptRun(xpos, ypos); + if (script >= 0) { + runNpcScript(script); + return; + } + if (_itemInHand != kItemNone) { + if (ypos < 155) { + if (hasClickedOnExit(xpos, ypos)) { + handleSceneChange(xpos, ypos, 1, 1); + return; + } + + dropItem(0, _itemInHand, xpos, ypos, 1); + } + } else { + if (ypos <= 155) + handleSceneChange(xpos, ypos, 1, 1); + } + } + } +} + +int KyraEngine_LoK::processInputHelper(int xpos, int ypos) { + uint8 item = findItemAtPos(xpos, ypos); + if (item != 0xFF) { + if (_itemInHand == kItemNone) { + _animator->animRemoveGameItem(item); + snd_playSoundEffect(53); + assert(_currentCharacter->sceneId < _roomTableSize); + Room *currentRoom = &_roomTable[_currentCharacter->sceneId]; + int item2 = currentRoom->itemsTable[item]; + currentRoom->itemsTable[item] = kItemNone; + setMouseItem(item2); + assert(_itemList && _takenList); + updateSentenceCommand(_itemList[getItemListIndex(item2)], _takenList[0], 179); + _itemInHand = item2; + clickEventHandler2(); + return 1; + } else { + exchangeItemWithMouseItem(_currentCharacter->sceneId, item); + return 1; + } + } + return 0; +} + +int KyraEngine_LoK::clickEventHandler(int xpos, int ypos) { + _emc->init(&_scriptClick, &_scriptClickData); + _scriptClick.regs[1] = xpos; + _scriptClick.regs[2] = ypos; + _scriptClick.regs[3] = 0; + _scriptClick.regs[4] = _itemInHand; + _emc->start(&_scriptClick, 1); + + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); + + return _scriptClick.regs[3]; +} + +void KyraEngine_LoK::updateMousePointer(bool forceUpdate) { + int shape = 0; + + int newMouseState = 0; + int newX = 0; + int newY = 0; + Common::Point mouse = getMousePos(); + if (mouse.y <= 158) { + if (mouse.x >= 12) { + if (mouse.x >= 308) { + if (_walkBlockEast == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -5; + shape = 3; + newX = 7; + newY = 5; + } + } else if (mouse.y >= 136) { + if (_walkBlockSouth == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -4; + shape = 4; + newX = 5; + newY = 7; + } + } else if (mouse.y < 12) { + if (_walkBlockNorth == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -6; + shape = 2; + newX = 5; + newY = 1; + } + } + } else { + if (_walkBlockWest == 0xFFFF) { + newMouseState = -2; + } else { + newMouseState = -3; + newX = 1; + newY = shape = 5; + } + } + } + + if (mouse.x >= _entranceMouseCursorTracks[0] && mouse.y >= _entranceMouseCursorTracks[1] + && mouse.x <= _entranceMouseCursorTracks[2] && mouse.y <= _entranceMouseCursorTracks[3]) { + switch (_entranceMouseCursorTracks[4]) { + case 0: + newMouseState = -6; + shape = 2; + newX = 5; + newY = 1; + break; + + case 2: + newMouseState = -5; + shape = 3; + newX = 7; + newY = 5; + break; + + case 4: + newMouseState = -4; + shape = 4; + newX = 5; + newY = 7; + break; + + case 6: + newMouseState = -3; + shape = 5; + newX = 1; + newY = 5; + break; + + default: + break; + } + } + + if (newMouseState == -2) { + shape = 6; + newX = 4; + newY = 4; + } + + if ((newMouseState && _mouseState != newMouseState) || (newMouseState && forceUpdate)) { + _mouseState = newMouseState; + _screen->setMouseCursor(newX, newY, _shapes[shape]); + } + + if (!newMouseState) { + if (_mouseState != _itemInHand || forceUpdate) { + if (mouse.y > 158 || (mouse.x >= 12 && mouse.x < 308 && mouse.y < 136 && mouse.y >= 12) || forceUpdate) { + _mouseState = _itemInHand; + if (_itemInHand == kItemNone) + _screen->setMouseCursor(1, 1, _shapes[0]); + else + _screen->setMouseCursor(8, 15, _shapes[216 + _itemInHand]); + } + } + } +} + +bool KyraEngine_LoK::hasClickedOnExit(int xpos, int ypos) { + if (xpos < 16 || xpos >= 304) + return true; + + if (ypos < 8) + return true; + + if (ypos < 136 || ypos > 155) + return false; + + return true; +} + +void KyraEngine_LoK::clickEventHandler2() { + Common::Point mouse = getMousePos(); + + _emc->init(&_scriptClick, &_scriptClickData); + _scriptClick.regs[0] = _currentCharacter->sceneId; + _scriptClick.regs[1] = mouse.x; + _scriptClick.regs[2] = mouse.y; + _scriptClick.regs[4] = _itemInHand; + _emc->start(&_scriptClick, 6); + + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); +} + +int KyraEngine_LoK::checkForNPCScriptRun(int xpos, int ypos) { + int returnValue = -1; + const Character *currentChar = _currentCharacter; + int charLeft = 0, charRight = 0, charTop = 0, charBottom = 0; + + int scaleFactor = _scaleTable[currentChar->y1]; + int addX = (((scaleFactor * 8) * 3) >> 8) >> 1; + int addY = ((scaleFactor * 3) << 4) >> 8; + + charLeft = currentChar->x1 - addX; + charRight = currentChar->x1 + addX; + charTop = currentChar->y1 - addY; + charBottom = currentChar->y1; + + if (xpos >= charLeft && charRight >= xpos && charTop <= ypos && charBottom >= ypos) + return 0; + + if (xpos > 304 || xpos < 16) + return -1; + + for (int i = 1; i < 5; ++i) { + currentChar = &_characterList[i]; + + if (currentChar->sceneId != _currentCharacter->sceneId) + continue; + + charLeft = currentChar->x1 - 12; + charRight = currentChar->x1 + 11; + charTop = currentChar->y1 - 48; + // if (!i) + // charBottom = currentChar->y2 - 16; + // else + charBottom = currentChar->y1; + + if (xpos < charLeft || xpos > charRight || ypos < charTop || charBottom < ypos) + continue; + + if (returnValue != -1) { + if (currentChar->y1 >= _characterList[returnValue].y1) + returnValue = i; + } else { + returnValue = i; + } + } + + return returnValue; +} + +void KyraEngine_LoK::runNpcScript(int func) { + _emc->init(&_npcScript, &_npcScriptData); + _emc->start(&_npcScript, func); + _npcScript.regs[0] = _currentCharacter->sceneId; + _npcScript.regs[4] = _itemInHand; + _npcScript.regs[5] = func; + + while (_emc->isValid(&_npcScript)) + _emc->run(&_npcScript); +} + +void KyraEngine_LoK::checkAmuletAnimFlags() { + if (_brandonStatusBit & 2) { + seq_makeBrandonNormal2(); + _timer->setCountdown(19, 300); + } + + if (_brandonStatusBit & 0x20) { + seq_makeBrandonNormal(); + _timer->setCountdown(19, 300); + } +} + +#pragma mark - + +void KyraEngine_LoK::registerDefaultSettings() { + KyraEngine_v1::registerDefaultSettings(); + + // Most settings already have sensible defaults. This one, however, is + // specific to the Kyra engine. + ConfMan.registerDefault("walkspeed", 2); +} + +void KyraEngine_LoK::readSettings() { + int talkspeed = ConfMan.getInt("talkspeed"); + + // The default talk speed is 60. This should be mapped to "Normal". + + if (talkspeed == 0) + _configTextspeed = 3; // Clickable + if (talkspeed <= 50) + _configTextspeed = 0; // Slow + else if (talkspeed <= 150) + _configTextspeed = 1; // Normal + else + _configTextspeed = 2; // Fast + + KyraEngine_v1::readSettings(); +} + +void KyraEngine_LoK::writeSettings() { + int talkspeed; + + switch (_configTextspeed) { + case 0: // Slow + talkspeed = 1; + break; + case 1: // Normal + talkspeed = 60; + break; + case 2: // Fast + talkspeed = 255; + break; + default: // Clickable + talkspeed = 0; + } + + ConfMan.setInt("talkspeed", talkspeed); + + KyraEngine_v1::writeSettings(); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/kyra_lok.h b/engines/kyra/engine/kyra_lok.h new file mode 100644 index 0000000000..05053877c4 --- /dev/null +++ b/engines/kyra/engine/kyra_lok.h @@ -0,0 +1,816 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_KYRA_LOK_H +#define KYRA_KYRA_LOK_H + +#include "kyra/kyra_v1.h" +#include "kyra/script/script.h" +#include "kyra/graphics/screen_lok.h" +#include "kyra/gui/gui_lok.h" +#include "kyra/engine/item.h" + +namespace Kyra { + +class Movie; +class SoundDigital; +class SeqPlayer; +class Sprites; +class Animator_LoK; +class TextDisplayer; +class KyraEngine_LoK; + +struct Character { + uint16 sceneId; + uint8 height; + uint8 facing; + uint16 currentAnimFrame; + int8 inventoryItems[10]; + int16 x1, y1, x2, y2; +}; + +struct Shape { + uint8 imageIndex; + int8 xOffset, yOffset; + uint8 x, y, w, h; +}; + +struct Room { + uint8 nameIndex; + uint16 northExit; + uint16 eastExit; + uint16 southExit; + uint16 westExit; + int8 itemsTable[12]; + uint16 itemsXPos[12]; + uint8 itemsYPos[12]; + uint8 needInit[12]; +}; + +struct SeqLoop { + const uint8 *ptr; + uint16 count; +}; + +struct SceneExits { + uint16 northXPos; + uint8 northYPos; + uint16 eastXPos; + uint8 eastYPos; + uint16 southXPos; + uint8 southYPos; + uint16 westXPos; + uint8 westYPos; +}; + +struct BeadState { + int16 x; + int16 y; + int16 width; + int16 height; + int16 dstX; + int16 dstY; + int16 width2; + int16 unk8; + int16 unk9; + int16 tableIndex; +}; + +class KyraEngine_LoK : public KyraEngine_v1 { + friend class MusicPlayer; + friend class Debugger_LoK; + friend class Animator_LoK; + friend class GUI_LoK; +public: + KyraEngine_LoK(OSystem *system, const GameFlags &flags); + ~KyraEngine_LoK(); + + // _sprites and _seqplayer should be paused here too, to avoid some animation glitches, + // also parts of the hardcoded Malcolm fight might need some special handling. + + Screen *screen() { return _screen; } + Animator_LoK *animator() { return _animator; } + GUI *gui() const { return _gui; } + virtual Movie *createWSAMovie(); + + uint8 **shapes() { return _shapes; } + Character *currentCharacter() { return _currentCharacter; } + Character *characterList() { return _characterList; } + uint16 brandonStatus() { return _brandonStatusBit; } + + // TODO: remove me with workaround in animator.cpp l209 + uint16 getScene() { return _currentRoom; } + + int _paletteChanged; + int16 _northExitHeight; + + typedef bool (KyraEngine_LoK::*IntroProc)(); + + // static data access + const char *const *seqWSATable() { return _seq_WSATable; } + const char *const *seqCPSTable() { return _seq_CPSTable; } + const char *const *seqCOLTable() { return _seq_COLTable; } + const char *const *seqTextsTable() { return _seq_textsTable; } + + const uint8 *const *palTable1() { return &_specialPalettes[0]; } + const uint8 *const *palTable2() { return &_specialPalettes[29]; } + +protected: + virtual Common::Error go(); + virtual Common::Error init(); + +public: + // sequences + // -> misc + bool seq_skipSequence() const; +protected: + // -> demo + void seq_demo(); + + // -> intro + void seq_intro(); + bool seq_introPublisherLogos(); + bool seq_introLogos(); + bool seq_introStory(); + bool seq_introMalcolmTree(); + bool seq_introKallakWriting(); + bool seq_introKallakMalcolm(); + + // -> ingame animations + void seq_createAmuletJewel(int jewel, int page, int noSound, int drawOnly); + void seq_brandonHealing(); + void seq_brandonHealing2(); + void seq_poisonDeathNow(int now); + void seq_poisonDeathNowAnim(); + void seq_playFluteAnimation(); + void seq_winterScroll1(); + void seq_winterScroll2(); + void seq_makeBrandonInv(); + void seq_makeBrandonNormal(); + void seq_makeBrandonNormal2(); + void seq_makeBrandonWisp(); + void seq_dispelMagicAnimation(); + void seq_fillFlaskWithWater(int item, int type); + void seq_playDrinkPotionAnim(int item, int unk2, int flags); + void seq_brandonToStone(); + + // -> end fight + int seq_playEnd(); + void seq_playEnding(); + + int handleMalcolmFlag(); + int handleBeadState(); + void initBeadState(int x, int y, int x2, int y2, int unk1, BeadState *ptr); + int processBead(int x, int y, int &x2, int &y2, BeadState *ptr); + + // -> credits + void seq_playCredits(); + void seq_playCreditsAmiga(); + +public: + // delay + void delayUntil(uint32 timestamp, bool updateGameTimers = false, bool update = false, bool isMainLoop = false); + void delay(uint32 millis, bool update = false, bool isMainLoop = false); + void delayWithTicks(int ticks); + + bool skipFlag() const; + void resetSkipFlag(bool removeEvent = true); + + // TODO + void registerDefaultSettings(); + void readSettings(); + void writeSettings(); + + void snd_playSoundEffect(int track, int volume=0xFF); + void snd_playWanderScoreViaMap(int command, int restart); + virtual void snd_playVoiceFile(int id); + void snd_voiceWaitForFinish(bool ingame = true); + uint32 snd_getVoicePlayTime(); + +protected: + int32 _speechPlayTime; + + Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail); + Common::Error loadGameState(int slot); +protected: + // input + void processInput(int xpos, int ypos); + int processInputHelper(int xpos, int ypos); + int clickEventHandler(int xpos, int ypos); + void clickEventHandler2(); + void updateMousePointer(bool forceUpdate = false); + bool hasClickedOnExit(int xpos, int ypos); + + // scene + // -> init + void loadSceneMsc(); + void startSceneScript(int brandonAlive); + void setupSceneItems(); + void initSceneData(int facing, int unk1, int brandonAlive); + void initSceneObjectList(int brandonAlive); + void initSceneScreen(int brandonAlive); + void setupSceneResource(int sceneId); + + // -> process + void enterNewScene(int sceneId, int facing, int unk1, int unk2, int brandonAlive); + int handleSceneChange(int xpos, int ypos, int unk1, int frameReset); + int processSceneChange(int *table, int unk1, int frameReset); + int changeScene(int facing); + + // -> modification + void transcendScenes(int roomIndex, int roomName); + void setSceneFile(int roomIndex, int roomName); + + // -> pathfinder + int findWay(int x, int y, int toX, int toY, int *moveTable, int moveTableSize); + bool lineIsPassable(int x, int y); + + // -> item handling + // --> misc + void addItemToRoom(uint16 sceneId, uint8 item, int itemIndex, int x, int y); + + // --> drop handling + void itemDropDown(int x, int y, int destX, int destY, byte freeItem, int item); + int processItemDrop(uint16 sceneId, uint8 item, int x, int y, int unk1, int unk2); + void dropItem(int unk1, int item, int x, int y, int unk2); + + // --> dropped item handling + int countItemsInScene(uint16 sceneId); + void exchangeItemWithMouseItem(uint16 sceneId, int itemIndex); + byte findFreeItemInScene(int scene); + byte findItemAtPos(int x, int y); + + // --> drop area handling + void addToNoDropRects(int x, int y, int w, int h); + void clearNoDropRects(); + int isDropable(int x, int y); + int checkNoDropRects(int x, int y); + + // --> player items handling + void updatePlayerItemsForScene(); + + // --> item GFX handling + void backUpItemRect0(int xpos, int ypos); + void restoreItemRect0(int xpos, int ypos); + void backUpItemRect1(int xpos, int ypos); + void restoreItemRect1(int xpos, int ypos); + + // items + // -> misc + void placeItemInGenericMapScene(int item, int index); + + // -> mouse item + void setHandItem(Item item); + void removeHandItem(); + void setMouseItem(Item item); + + int getItemListIndex(Item item); + + // -> graphics effects + void wipeDownMouseItem(int xpos, int ypos); + void itemSpecialFX(int x, int y, int item); + void itemSpecialFX1(int x, int y, int item); + void itemSpecialFX2(int x, int y, int item); + void magicOutMouseItem(int animIndex, int itemPos); + void magicInMouseItem(int animIndex, int item, int itemPos); + void specialMouseItemFX(int shape, int x, int y, int animIndex, int tableIndex, int loopStart, int maxLoops); + void processSpecialMouseItemFX(int shape, int x, int y, int tableValue, int loopStart, int maxLoops); + + // character + // -> movement + void moveCharacterToPos(int character, int facing, int xpos, int ypos); + void setCharacterPositionWithUpdate(int character); + int setCharacterPosition(int character, int *facingTable); + void setCharacterPositionHelper(int character, int *facingTable); + void setCharactersPositions(int character); + + // -> brandon + void setBrandonPoisonFlags(int reset); + void resetBrandonPoisonFlags(); + + // chat + // -> process + void characterSays(int vocFile, const char *chatStr, int8 charNum, int8 chatDuration); + void waitForChatToFinish(int vocFile, int16 chatDuration, const char *str, uint8 charNum, const bool printText); + + // -> initialization + int initCharacterChat(int8 charNum); + void backupChatPartnerAnimFrame(int8 charNum); + void restoreChatPartnerAnimFrame(int8 charNum); + int8 getChatPartnerNum(); + + // -> deinitialization + void endCharacterChat(int8 charNum, int16 arg_4); + + // graphics + // -> misc + int findDuplicateItemShape(int shape); + void updateKyragemFading(); + + // -> interface + void loadMainScreen(int page = 3); + void redrawInventory(int page); +public: + void drawSentenceCommand(const char *sentence, int unk1); + void updateSentenceCommand(const char *str1, const char *str2, int unk1); + void updateTextFade(); + +protected: + // -> amulet + void drawJewelPress(int jewel, int drawSpecial); + void drawJewelsFadeOutStart(); + void drawJewelsFadeOutEnd(int jewel); + + // -> shape handling + void setupShapes123(const Shape *shapeTable, int endShape, int flags); + void freeShapes123(); + + // misc (TODO) + void startup(); + void mainLoop(); + + int checkForNPCScriptRun(int xpos, int ypos); + void runNpcScript(int func); + + void loadMouseShapes(); + void loadCharacterShapes(); + void loadSpecialEffectShapes(); + void loadItems(); + void loadButtonShapes(); + void initMainButtonList(); + void setCharactersInDefaultScene(); + void setupPanPages(); + void freePanPages(); + void closeFinalWsa(); + + //void setTimer19(); + void setupTimers(); + void timerUpdateHeadAnims(int timerNum); + void timerTulipCreator(int timerNum); + void timerRubyCreator(int timerNum); + void timerAsInvisibleTimeout(int timerNum); + void timerAsWillowispTimeout(int timerNum); + void checkAmuletAnimFlags(); + void timerRedrawAmulet(int timerNum); + void timerLavenderRoseCreator(int timerNum); + void timerAcornCreator(int timerNum); + void timerBlueberryCreator(int timerNum); + void timerFadeText(int timerNum); + void timerWillowispFrameTimer(int timerNum); + void timerInvisibleFrameTimer(int timerNum); + void drawAmulet(); + void setTextFadeTimerCountdown(int16 countdown); + void setWalkspeed(uint8 newSpeed); + + void setItemCreationFlags(int offset, int count); + + int buttonInventoryCallback(Button *caller); + int buttonAmuletCallback(Button *caller); + + bool _seqPlayerFlag; + bool _skipIntroFlag; + bool _abortIntroFlag; + + bool _menuDirectlyToLoad; + uint8 *_itemBkgBackUp[2]; + uint8 *_shapes[373]; + Item _itemInHand; + bool _changedScene; + int _unkScreenVar1, _unkScreenVar2, _unkScreenVar3; + int _beadStateVar; + int _unkAmuletVar; + + int _malcolmFlag; + int _endSequenceSkipFlag; + int _endSequenceNeedLoading; + int _unkEndSeqVar2; + uint8 *_endSequenceBackUpRect; + int _unkEndSeqVar4; + int _unkEndSeqVar5; + int _lastDisplayedPanPage; + uint8 *_panPagesTable[20]; + Movie *_finalA, *_finalB, *_finalC; + + Movie *_movieObjects[10]; + + uint16 _entranceMouseCursorTracks[5]; + uint16 _walkBlockNorth; + uint16 _walkBlockEast; + uint16 _walkBlockSouth; + uint16 _walkBlockWest; + + int32 _scaleMode; + int16 _scaleTable[145]; + + Common::Rect _noDropRects[11]; + + int8 _birthstoneGemTable[4]; + int8 _idolGemsTable[3]; + + int8 _marbleVaseItem; + int8 _foyerItemTable[3]; + + int8 _cauldronState; + int8 _crystalState[2]; + + uint16 _brandonStatusBit; + uint8 _brandonStatusBit0x02Flag; + uint8 _brandonStatusBit0x20Flag; + uint8 _brandonPoisonFlagsGFX[256]; + int16 _brandonInvFlag; + uint8 _poisonDeathCounter; + int _brandonPosX; + int _brandonPosY; + + uint16 _currentChatPartnerBackupFrame; + uint16 _currentCharAnimFrame; + + int _characterFacingZeroCount[8]; + int _characterFacingFourCount[8]; + + int8 *_sceneAnimTable[50]; + + uint8 _itemHtDat[145]; + int _lastProcessedItem; + int _lastProcessedItemHeight; + + int16 *_exitListPtr; + int16 _exitList[11]; + SceneExits _sceneExits; + uint16 _currentRoom; + int _scenePhasingFlag; + + int _sceneChangeState; + int _loopFlag2; + + int _pathfinderFlag; + int _pathfinderFlag2; + int _lastFindWayRet; + int *_movFacingTable; + + int8 _talkingCharNum; + int8 _charSayUnk2; + int8 _charSayUnk3; + int8 _currHeadShape; + int _currentHeadFrameTableIndex; + int8 _disabledTalkAnimObject; + int8 _enabledTalkAnimObject; + uint8 _currSentenceColor[3]; + int8 _startSentencePalIndex; + bool _fadeText; + + uint8 _configTextspeed; + + Animator_LoK *_animator; + SeqPlayer *_seq; + Sprites *_sprites; + Screen_LoK *_screen; + + EMCState _scriptMain; + + EMCState _npcScript; + EMCData _npcScriptData; + + EMCState _scriptClick; + EMCData _scriptClickData; + + Character *_characterList; + Character *_currentCharacter; + + Button *_buttonList; + GUI_LoK *_gui; + + uint16 _malcolmFrame; + uint32 _malcolmTimer1; + uint32 _malcolmTimer2; + + uint32 _beadStateTimer1; + uint32 _beadStateTimer2; + BeadState _beadState1; + BeadState _beadState2; + + struct KyragemState { + uint16 nextOperation; + uint16 rOffset; + uint16 gOffset; + uint16 bOffset; + uint32 timerCount; + } _kyragemFadingState; + + static const int8 _dosTrackMap[]; + static const int _dosTrackMapSize; + + static const int8 _amigaTrackMap[]; + static const int _amigaTrackMapSize; + + // TODO: get rid of all variables having pointers to the static resources if possible + // i.e. let them directly use the _staticres functions + void initStaticResource(); + + const uint8 *_seq_Forest; + const uint8 *_seq_KallakWriting; + const uint8 *_seq_KyrandiaLogo; + const uint8 *_seq_KallakMalcolm; + const uint8 *_seq_MalcolmTree; + const uint8 *_seq_WestwoodLogo; + const uint8 *_seq_Demo1; + const uint8 *_seq_Demo2; + const uint8 *_seq_Demo3; + const uint8 *_seq_Demo4; + const uint8 *_seq_Reunion; + + const char *const *_seq_WSATable; + const char *const *_seq_CPSTable; + const char *const *_seq_COLTable; + const char *const *_seq_textsTable; + + const char *const *_storyStrings; + + int _seq_WSATable_Size; + int _seq_CPSTable_Size; + int _seq_COLTable_Size; + int _seq_textsTable_Size; + + int _storyStringsSize; + + const char *const *_itemList; + const char *const *_takenList; + const char *const *_placedList; + const char *const *_droppedList; + const char *const *_noDropList; + const char *const *_putDownFirst; + const char *const *_waitForAmulet; + const char *const *_blackJewel; + const char *const *_poisonGone; + const char *const *_healingTip; + const char *const *_thePoison; + const char *const *_fluteString; + const char *const *_wispJewelStrings; + const char *const *_magicJewelString; + const char *const *_flaskFull; + const char *const *_fullFlask; + const char *const *_veryClever; + const char *const *_homeString; + const char *const *_newGameString; + + int _itemList_Size; + int _takenList_Size; + int _placedList_Size; + int _droppedList_Size; + int _noDropList_Size; + int _putDownFirst_Size; + int _waitForAmulet_Size; + int _blackJewel_Size; + int _poisonGone_Size; + int _healingTip_Size; + int _thePoison_Size; + int _fluteString_Size; + int _wispJewelStrings_Size; + int _magicJewelString_Size; + int _flaskFull_Size; + int _fullFlask_Size; + int _veryClever_Size; + int _homeString_Size; + int _newGameString_Size; + + const char *const *_characterImageTable; + int _characterImageTableSize; + + const char *const *_guiStrings; + int _guiStringsSize; + + const char *const *_configStrings; + int _configStringsSize; + + Shape *_defaultShapeTable; + int _defaultShapeTableSize; + + const Shape *_healingShapeTable; + int _healingShapeTableSize; + const Shape *_healingShape2Table; + int _healingShape2TableSize; + + const Shape *_posionDeathShapeTable; + int _posionDeathShapeTableSize; + + const Shape *_fluteAnimShapeTable; + int _fluteAnimShapeTableSize; + + const Shape *_winterScrollTable; + int _winterScrollTableSize; + const Shape *_winterScroll1Table; + int _winterScroll1TableSize; + const Shape *_winterScroll2Table; + int _winterScroll2TableSize; + + const Shape *_drinkAnimationTable; + int _drinkAnimationTableSize; + + const Shape *_brandonToWispTable; + int _brandonToWispTableSize; + + const Shape *_magicAnimationTable; + int _magicAnimationTableSize; + + const Shape *_brandonStoneTable; + int _brandonStoneTableSize; + + Room *_roomTable; + int _roomTableSize; + const char *const *_roomFilenameTable; + int _roomFilenameTableSize; + + const uint8 *_amuleteAnim; + + const uint8 *const *_specialPalettes; + + // positions of the inventory + static const uint16 _itemPosX[]; + static const uint8 _itemPosY[]; + + void setupButtonData(); + Button *_buttonData; + Button **_buttonDataListPtr; + + static const uint8 _magicMouseItemStartFrame[]; + static const uint8 _magicMouseItemEndFrame[]; + static const uint8 _magicMouseItemStartFrame2[]; + static const uint8 _magicMouseItemEndFrame2[]; + + static const uint16 _amuletX[]; + static const uint16 _amuletY[]; + static const uint16 _amuletX2[]; + static const uint16 _amuletY2[]; + + // special palette handling for AMIGA + void setupZanthiaPalette(int pal); +protected: + void setupOpcodeTable(); + + // Opcodes + int o1_magicInMouseItem(EMCState *script); + int o1_characterSays(EMCState *script); + int o1_delay(EMCState *script); + int o1_drawSceneAnimShape(EMCState *script); + int o1_runNPCScript(EMCState *script); + int o1_setSpecialExitList(EMCState *script); + int o1_walkPlayerToPoint(EMCState *script); + int o1_dropItemInScene(EMCState *script); + int o1_drawAnimShapeIntoScene(EMCState *script); + int o1_savePageToDisk(EMCState *script); + int o1_sceneAnimOn(EMCState *script); + int o1_sceneAnimOff(EMCState *script); + int o1_getElapsedSeconds(EMCState *script); + int o1_mouseIsPointer(EMCState *script); + int o1_runSceneAnimUntilDone(EMCState *script); + int o1_fadeSpecialPalette(EMCState *script); + int o1_phaseInSameScene(EMCState *script); + int o1_setScenePhasingFlag(EMCState *script); + int o1_resetScenePhasingFlag(EMCState *script); + int o1_queryScenePhasingFlag(EMCState *script); + int o1_sceneToDirection(EMCState *script); + int o1_setBirthstoneGem(EMCState *script); + int o1_placeItemInGenericMapScene(EMCState *script); + int o1_setBrandonStatusBit(EMCState *script); + int o1_delaySecs(EMCState *script); + int o1_getCharacterScene(EMCState *script); + int o1_runNPCSubscript(EMCState *script); + int o1_magicOutMouseItem(EMCState *script); + int o1_internalAnimOn(EMCState *script); + int o1_forceBrandonToNormal(EMCState *script); + int o1_poisonDeathNow(EMCState *script); + int o1_setScaleMode(EMCState *script); + int o1_openWSAFile(EMCState *script); + int o1_closeWSAFile(EMCState *script); + int o1_runWSAFromBeginningToEnd(EMCState *script); + int o1_displayWSAFrame(EMCState *script); + int o1_enterNewScene(EMCState *script); + int o1_setSpecialEnterXAndY(EMCState *script); + int o1_runWSAFrames(EMCState *script); + int o1_popBrandonIntoScene(EMCState *script); + int o1_restoreAllObjectBackgrounds(EMCState *script); + int o1_setCustomPaletteRange(EMCState *script); + int o1_loadPageFromDisk(EMCState *script); + int o1_customPrintTalkString(EMCState *script); + int o1_restoreCustomPrintBackground(EMCState *script); + int o1_getCharacterX(EMCState *script); + int o1_getCharacterY(EMCState *script); + int o1_setCharacterFacing(EMCState *script); + int o1_copyWSARegion(EMCState *script); + int o1_printText(EMCState *script); + int o1_loadSoundFile(EMCState *script); + int o1_displayWSAFrameOnHidPage(EMCState *script); + int o1_displayWSASequentialFrames(EMCState *script); + int o1_refreshCharacter(EMCState *script); + int o1_internalAnimOff(EMCState *script); + int o1_changeCharactersXAndY(EMCState *script); + int o1_clearSceneAnimatorBeacon(EMCState *script); + int o1_querySceneAnimatorBeacon(EMCState *script); + int o1_refreshSceneAnimator(EMCState *script); + int o1_placeItemInOffScene(EMCState *script); + int o1_wipeDownMouseItem(EMCState *script); + int o1_placeCharacterInOtherScene(EMCState *script); + int o1_getKey(EMCState *script); + int o1_specificItemInInventory(EMCState *script); + int o1_popMobileNPCIntoScene(EMCState *script); + int o1_mobileCharacterInScene(EMCState *script); + int o1_hideMobileCharacter(EMCState *script); + int o1_unhideMobileCharacter(EMCState *script); + int o1_setCharacterLocation(EMCState *script); + int o1_walkCharacterToPoint(EMCState *script); + int o1_specialEventDisplayBrynnsNote(EMCState *script); + int o1_specialEventRemoveBrynnsNote(EMCState *script); + int o1_setLogicPage(EMCState *script); + int o1_fatPrint(EMCState *script); + int o1_preserveAllObjectBackgrounds(EMCState *script); + int o1_updateSceneAnimations(EMCState *script); + int o1_sceneAnimationActive(EMCState *script); + int o1_setCharacterMovementDelay(EMCState *script); + int o1_getCharacterFacing(EMCState *script); + int o1_bkgdScrollSceneAndMasksRight(EMCState *script); + int o1_dispelMagicAnimation(EMCState *script); + int o1_findBrightestFireberry(EMCState *script); + int o1_setFireberryGlowPalette(EMCState *script); + int o1_setDeathHandlerFlag(EMCState *script); + int o1_drinkPotionAnimation(EMCState *script); + int o1_makeAmuletAppear(EMCState *script); + int o1_drawItemShapeIntoScene(EMCState *script); + int o1_setCharacterCurrentFrame(EMCState *script); + int o1_waitForConfirmationMouseClick(EMCState *script); + int o1_pageFlip(EMCState *script); + int o1_setSceneFile(EMCState *script); + int o1_getItemInMarbleVase(EMCState *script); + int o1_setItemInMarbleVase(EMCState *script); + int o1_addItemToInventory(EMCState *script); + int o1_intPrint(EMCState *script); + int o1_shakeScreen(EMCState *script); + int o1_createAmuletJewel(EMCState *script); + int o1_setSceneAnimCurrXY(EMCState *script); + int o1_poisonBrandonAndRemaps(EMCState *script); + int o1_fillFlaskWithWater(EMCState *script); + int o1_getCharacterMovementDelay(EMCState *script); + int o1_getBirthstoneGem(EMCState *script); + int o1_queryBrandonStatusBit(EMCState *script); + int o1_playFluteAnimation(EMCState *script); + int o1_playWinterScrollSequence(EMCState *script); + int o1_getIdolGem(EMCState *script); + int o1_setIdolGem(EMCState *script); + int o1_totalItemsInScene(EMCState *script); + int o1_restoreBrandonsMovementDelay(EMCState *script); + int o1_setEntranceMouseCursorTrack(EMCState *script); + int o1_itemAppearsOnGround(EMCState *script); + int o1_setNoDrawShapesFlag(EMCState *script); + int o1_fadeEntirePalette(EMCState *script); + int o1_itemOnGroundHere(EMCState *script); + int o1_queryCauldronState(EMCState *script); + int o1_setCauldronState(EMCState *script); + int o1_queryCrystalState(EMCState *script); + int o1_setCrystalState(EMCState *script); + int o1_setPaletteRange(EMCState *script); + int o1_shrinkBrandonDown(EMCState *script); + int o1_growBrandonUp(EMCState *script); + int o1_setBrandonScaleXAndY(EMCState *script); + int o1_resetScaleMode(EMCState *script); + int o1_getScaleDepthTableValue(EMCState *script); + int o1_setScaleDepthTableValue(EMCState *script); + int o1_message(EMCState *script); + int o1_checkClickOnNPC(EMCState *script); + int o1_getFoyerItem(EMCState *script); + int o1_setFoyerItem(EMCState *script); + int o1_setNoItemDropRegion(EMCState *script); + int o1_walkMalcolmOn(EMCState *script); + int o1_passiveProtection(EMCState *script); + int o1_setPlayingLoop(EMCState *script); + int o1_brandonToStoneSequence(EMCState *script); + int o1_brandonHealingSequence(EMCState *script); + int o1_protectCommandLine(EMCState *script); + int o1_pauseMusicSeconds(EMCState *script); + int o1_resetMaskRegion(EMCState *script); + int o1_setPaletteChangeFlag(EMCState *script); + int o1_vocUnload(EMCState *script); + int o1_vocLoad(EMCState *script); + int o1_dummy(EMCState *script); +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/kyra_mr.cpp b/engines/kyra/engine/kyra_mr.cpp new file mode 100644 index 0000000000..9cadf3c626 --- /dev/null +++ b/engines/kyra/engine/kyra_mr.cpp @@ -0,0 +1,1426 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_mr.h" +#include "kyra/graphics/wsamovie.h" +#include "kyra/text/text_mr.h" +#include "kyra/graphics/vqa.h" +#include "kyra/engine/timer.h" +#include "kyra/gui/debugger.h" +#include "kyra/gui/gui_mr.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound_digital.h" + +#include "common/system.h" +#include "common/config-manager.h" + +namespace Kyra { + +const KyraEngine_v2::EngineDesc KyraEngine_MR::_mrEngineDesc = { + // Generic shape related + 248, + KyraEngine_MR::_characterFrameTable, + + // Scene script + 9, + + // Animation script specific + 9, + + // Item specific + 71 +}; + +KyraEngine_MR::KyraEngine_MR(OSystem *system, const GameFlags &flags) : KyraEngine_v2(system, flags, _mrEngineDesc) { + _soundDigital = 0; + _musicSoundChannel = -1; + _menuAudioFile = "TITLE1"; + _lastMusicCommand = -1; + _itemBuffer1 = _itemBuffer2 = 0; + _scoreFile = 0; + _cCodeFile = 0; + _scenesFile = 0; + _itemFile = 0; + _gamePlayBuffer = 0; + _interface = _interfaceCommandLine = 0; + _costPalBuffer = 0; + memset(_sceneShapes, 0, sizeof(_sceneShapes)); + memset(_sceneAnimMovie, 0, sizeof(_sceneAnimMovie)); + _gfxBackUpRect = 0; + _paletteOverlay = 0; + _sceneList = 0; + _mainCharacter.sceneId = 9; + _mainCharacter.height = 0x4C; + _mainCharacter.facing = 5; + _mainCharacter.animFrame = 0x57; + _mainCharacter.walkspeed = 5; + memset(_activeItemAnim, 0, sizeof(_activeItemAnim)); + _nextAnimItem = 0; + _text = 0; + _commandLineY = 189; + _inventoryState = false; + memset(_characterAnimTable, 0, sizeof(_characterAnimTable)); + _overwriteSceneFacing = false; + _maskPageMinY = _maskPageMaxY = 0; + _sceneStrings = 0; + _enterNewSceneLock = 0; + _mainCharX = _mainCharY = -1; + _animList = 0; + _drawNoShapeFlag = false; + _wasPlayingVQA = false; + _lastCharPalLayer = -1; + _charPalUpdate = false; + _runFlag = false; + _unk5 = 0; + _unkSceneScreenFlag1 = false; + _noScriptEnter = true; + _itemInHand = _mouseState = kItemNone; + _savedMouseState = -1; + _unk4 = 0; + _loadingState = false; + _noStartupChat = false; + _pathfinderFlag = 0; + _talkObjectList = 0; + memset(&_chatScriptState, 0, sizeof(_chatScriptState)); + memset(&_chatScriptData, 0, sizeof(_chatScriptData)); + _voiceSoundChannel = -1; + _charBackUpWidth2 = _charBackUpHeight2 = -1; + _charBackUpWidth = _charBackUpHeight = -1; + _useActorBuffer = false; + _curStudioSFX = 283; + _badConscienceShown = false; + _currentChapter = 1; + _unkHandleSceneChangeFlag = false; + memset(_sceneShapeDescs, 0, sizeof(_sceneShapeDescs)); + _cnvFile = _dlgBuffer = 0; + _curDlgChapter = _curDlgIndex = _curDlgLang = -1; + _isStartupDialog = 0; + _stringBuffer = 0; + _menu = 0; + _menuAnim = 0; + _dialogSceneAnim = _dialogSceneScript = -1; + memset(&_dialogScriptData, 0, sizeof(_dialogScriptData)); + memset(&_dialogScriptState, 0, sizeof(_dialogScriptState)); + _dialogScriptFuncStart = _dialogScriptFuncProc = _dialogScriptFuncEnd = 0; + _malcolmsMood = 1; + _nextIdleAnim = 0; + _nextIdleType = false; + _inventoryScrollSpeed = -1; + _invWsa = 0; + _invWsaFrame = -1; + _score = 0; + memset(_scoreFlagTable, 0, sizeof(_scoreFlagTable)); + _mainButtonData = 0; + _mainButtonList = 0; + _mainButtonListInitialized = false; + _enableInventory = true; + _goodConscienceShown = false; + _goodConscienceAnim = -1; + _goodConsciencePosition = false; + _menuDirectlyToLoad = false; + _optionsFile = 0; + _actorFile = 0; + _chatAltFlag = false; + _albumChatActive = false; + memset(&_album, 0, sizeof(_album)); + _configHelium = false; + _fadeOutMusicChannel = -1; + memset(_scaleTable, 0, sizeof(_scaleTable)); +} + +KyraEngine_MR::~KyraEngine_MR() { + uninitMainMenu(); + + delete _screen; + delete _soundDigital; + + delete[] _itemBuffer1; + delete[] _itemBuffer2; + delete[] _scoreFile; + delete[] _cCodeFile; + delete[] _scenesFile; + delete[] _itemFile; + delete[] _actorFile; + delete[] _gamePlayBuffer; + delete[] _interface; + delete[] _interfaceCommandLine; + delete[] _costPalBuffer; + + for (uint i = 0; i < ARRAYSIZE(_sceneShapes); ++i) + delete[] _sceneShapes[i]; + + for (uint i = 0; i < ARRAYSIZE(_sceneAnimMovie); ++i) + delete _sceneAnimMovie[i]; + + delete[] _gfxBackUpRect; + delete[] _paletteOverlay; + + for (ShapeMap::iterator i = _gameShapes.begin(); i != _gameShapes.end(); ++i) { + delete[] i->_value; + i->_value = 0; + } + _gameShapes.clear(); + + delete[] _sceneStrings; + delete[] _talkObjectList; + + for (Common::Array::iterator i = _opcodesDialog.begin(); i != _opcodesDialog.end(); ++i) + delete *i; + _opcodesDialog.clear(); + + delete _cnvFile; + delete _dlgBuffer; + delete[] _stringBuffer; + delete _invWsa; + delete[] _mainButtonData; + delete _gui; + delete[] _optionsFile; + + delete _album.wsa; + delete _album.leftPage.wsa; + delete _album.rightPage.wsa; +} + +Common::Error KyraEngine_MR::init() { + _screen = new Screen_MR(this, _system); + assert(_screen); + _screen->setResolution(); + + _debugger = new Debugger_v2(this); + assert(_debugger); + + KyraEngine_v1::init(); + initStaticResource(); + + _soundDigital = new SoundDigital(this, _mixer); + assert(_soundDigital); + KyraEngine_v1::_text = _text = new TextDisplayer_MR(this, _screen); + assert(_text); + _gui = new GUI_MR(this); + assert(_gui); + _gui->initStaticData(); + + _screen->loadFont(Screen::FID_6_FNT, "6.FNT"); + _screen->loadFont(Screen::FID_8_FNT, "8FAT.FNT"); + _screen->loadFont(Screen::FID_BOOKFONT_FNT, "BOOKFONT.FNT"); + _screen->setFont(Screen::FID_8_FNT); + _screen->setAnimBlockPtr(3500); + _screen->setScreenDim(0); + + _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); + _screen->setScreenPalette(_screen->getPalette(0)); + + return Common::kNoError; +} + +Common::Error KyraEngine_MR::go() { + bool running = true; + preinit(); + _screen->hideMouse(); + initMainMenu(); + + _screen->clearPage(0); + _screen->clearPage(2); + + const bool firstTimeGame = !saveFileLoadable(0); + + if (firstTimeGame) { + playVQA("K3INTRO"); + _wasPlayingVQA = false; + } + + if (_gameToLoad != -1 || firstTimeGame) { + while (!_screen->isMouseVisible()) + _screen->showMouse(); + + uninitMainMenu(); + _musicSoundChannel = -1; + startup(); + runLoop(); + running = false; + } + + while (running && !shouldQuit()) { + _screen->_curPage = 0; + _screen->clearPage(0); + + _screen->setScreenPalette(_screen->getPalette(0)); + + playMenuAudioFile(); + + for (int i = 0; i < 64 && !shouldQuit(); ++i) { + uint32 nextRun = _system->getMillis() + 3 * _tickLength; + _menuAnim->displayFrame(i, 0, 0, 0, 0, 0, 0); + _screen->updateScreen(); + delayUntil(nextRun); + } + + for (int i = 64; i > 29 && !shouldQuit(); --i) { + uint32 nextRun = _system->getMillis() + 3 * _tickLength; + _menuAnim->displayFrame(i, 0, 0, 0, 0, 0, 0); + _screen->updateScreen(); + delayUntil(nextRun); + } + + _eventList.clear(); + + switch (_menu->handle(3)) { + case 2: + _menuDirectlyToLoad = true; + // fall through + + case 0: + uninitMainMenu(); + + fadeOutMusic(60); + _screen->fadeToBlack(60); + _musicSoundChannel = -1; + startup(); + runLoop(); + running = false; + break; + + case 1: + playVQA("K3INTRO"); + _wasPlayingVQA = false; + _screen->hideMouse(); + break; + + case 3: + fadeOutMusic(60); + _screen->fadeToBlack(60); + uninitMainMenu(); + quitGame(); + running = false; + break; + + default: + break; + } + } + + if (_showOutro && !shouldQuit()) + playVQA("CREDITS"); + + return Common::kNoError; +} + +void KyraEngine_MR::initMainMenu() { + _menuAnim = new WSAMovie_v2(this); + _menuAnim->open("REVENGE.WSA", 1, &_screen->getPalette(0)); + _screen->getPalette(0).fill(0, 1, 0); + + _menu = new MainMenu(this); + MainMenu::StaticData data = { + { _mainMenuStrings[_lang*4+0], _mainMenuStrings[_lang*4+1], _mainMenuStrings[_lang*4+2], _mainMenuStrings[_lang*4+3], 0 }, + { 0x01, 0x04, 0x0C, 0x04, 0x00, 0x80, 0xFF }, + { 0x16, 0x19, 0x1A, 0x16 }, + Screen::FID_8_FNT, 240 + }; + + if (_flags.lang == Common::ES_ESP) { + for (int i = 0; i < 4; ++i) + data.strings[i] = _mainMenuSpanishFan[i]; + } else if (_flags.lang == Common::IT_ITA) { + for (int i = 0; i < 4; ++i) + data.strings[i] = _mainMenuItalianFan[i]; + } + + MainMenu::Animation anim; + anim.anim = _menuAnim; + anim.startFrame = 29; + anim.endFrame = 63; + anim.delay = 2; + + _menu->init(data, anim); +} + +void KyraEngine_MR::uninitMainMenu() { + delete _menuAnim; + _menuAnim = 0; + delete _menu; + _menu = 0; +} + +void KyraEngine_MR::playVQA(const char *name) { + VQAMovie vqa(this, _system); + + Common::String filename = Common::String::format("%s%d.VQA", name, _configVQAQuality); + + if (vqa.open(filename.c_str())) { + for (int i = 0; i < 4; ++i) { + if (i != _musicSoundChannel) + _soundDigital->stopSound(i); + } + + _screen->hideMouse(); + _screen->copyPalette(1, 0); + fadeOutMusic(60); + _screen->fadeToBlack(60); + _screen->clearPage(0); + + vqa.play(); + vqa.close(); + + _soundDigital->stopAllSounds(); + _screen->showMouse(); + + // Taken from original, it used '1' here too + _screen->getPalette(0).fill(0, 256, 1); + _screen->setScreenPalette(_screen->getPalette(0)); + _screen->clearPage(0); + _screen->copyPalette(0, 1); + _wasPlayingVQA = true; + } +} + +#pragma mark - + +void KyraEngine_MR::playMenuAudioFile() { + if (_soundDigital->isPlaying(_musicSoundChannel)) + return; + + _musicSoundChannel = _soundDigital->playSound(_menuAudioFile, 0xFF, Audio::Mixer::kMusicSoundType, 255, true); +} + +void KyraEngine_MR::snd_playWanderScoreViaMap(int track, int force) { + if (_musicSoundChannel != -1 && !_soundDigital->isPlaying(_musicSoundChannel)) + force = 1; + else if (_musicSoundChannel == -1) + force = 1; + + if (track == _lastMusicCommand && !force) + return; + + stopMusicTrack(); + + if (_musicSoundChannel == -1) { + assert(track < _soundListSize && track >= 0); + + _musicSoundChannel = _soundDigital->playSound(_soundList[track], 0xFF, Audio::Mixer::kMusicSoundType, 255, true); + } + + _lastMusicCommand = track; +} + +void KyraEngine_MR::stopMusicTrack() { + if (_musicSoundChannel != -1 && _soundDigital->isPlaying(_musicSoundChannel)) + _soundDigital->stopSound(_musicSoundChannel); + + _lastMusicCommand = -1; + _musicSoundChannel = -1; +} + +void KyraEngine_MR::fadeOutMusic(int ticks) { + if (_musicSoundChannel >= 0) { + _fadeOutMusicChannel = _musicSoundChannel; + _soundDigital->beginFadeOut(_musicSoundChannel, ticks); + _lastMusicCommand = -1; + } +} + +void KyraEngine_MR::snd_playSoundEffect(int item, int volume) { + if (_sfxFileMap[item*2+0] != 0xFF) { + assert(_sfxFileMap[item*2+0] < _sfxFileListSize); + Common::String filename = Common::String::format("%s", _sfxFileList[_sfxFileMap[item*2+0]]); + uint8 priority = _sfxFileMap[item*2+1]; + + _soundDigital->playSound(filename.c_str(), priority, Audio::Mixer::kSFXSoundType, volume); + } +} + +void KyraEngine_MR::playVoice(int high, int low) { + snd_playVoiceFile(high * 1000 + low); +} + +void KyraEngine_MR::snd_playVoiceFile(int file) { + Common::String filename = Common::String::format("%.08u", (uint)file); + + if (speechEnabled()) + _voiceSoundChannel = _soundDigital->playSound(filename.c_str(), 0xFE, Audio::Mixer::kSpeechSoundType, 255); +} + +bool KyraEngine_MR::snd_voiceIsPlaying() { + return _soundDigital->isPlaying(_voiceSoundChannel); +} + +void KyraEngine_MR::snd_stopVoice() { + if (_voiceSoundChannel != -1) + _soundDigital->stopSound(_voiceSoundChannel); +} + +void KyraEngine_MR::playStudioSFX(const char *str) { + if (!_configStudio) + return; + + if (_rnd.getRandomNumberRng(1, 2) != 2) + return; + + const int strSize = strlen(str) - 1; + if (str[strSize] != '?' && str[strSize] != '!') + return; + + snd_playSoundEffect(_curStudioSFX++, 128); + + if (_curStudioSFX > 291) + _curStudioSFX = 283; +} + +#pragma mark - + +void KyraEngine_MR::preinit() { + _itemBuffer1 = new int8[72]; + _itemBuffer2 = new int8[144]; + initMouseShapes(); + initItems(); + + _screen->setMouseCursor(0, 0, _gameShapes[0]); +} + +void KyraEngine_MR::initMouseShapes() { + uint8 *data = _res->fileData("MOUSE.SHP", 0); + assert(data); + for (int i = 0; i <= 6; ++i) + _gameShapes[i] = _screen->makeShapeCopy(data, i); + delete[] data; +} + +void KyraEngine_MR::startup() { + _album.wsa = new WSAMovie_v2(this); + assert(_album.wsa); + _album.leftPage.wsa = new WSAMovie_v2(this); + assert(_album.leftPage.wsa); + _album.rightPage.wsa = new WSAMovie_v2(this); + assert(_album.rightPage.wsa); + + _gamePlayBuffer = new uint8[64000]; + + _interface = new uint8[17920]; + _interfaceCommandLine = new uint8[3840]; + + _screen->setFont(Screen::FID_8_FNT); + + _stringBuffer = new char[500]; + allocAnimObjects(1, 16, 50); + + memset(_sceneShapes, 0, sizeof(_sceneShapes)); + _screenBuffer = new uint8[64000]; + + if (!loadLanguageFile("ITEMS.", _itemFile)) + error("Couldn't load ITEMS"); + if (!loadLanguageFile("SCORE.", _scoreFile)) + error("Couldn't load SCORE"); + if (!loadLanguageFile("C_CODE.", _cCodeFile)) + error("Couldn't load C_CODE"); + if (!loadLanguageFile("SCENES.", _scenesFile)) + error("Couldn't load SCENES"); + if (!loadLanguageFile("OPTIONS.", _optionsFile)) + error("Couldn't load OPTIONS"); + if (!loadLanguageFile("_ACTOR.", _actorFile)) + error("couldn't load _ACTOR"); + + openTalkFile(0); + _currentTalkFile = 0; + openTalkFile(1); + loadCostPal(); + + for (int i = 0; i < 16; ++i) { + _sceneAnims[i].flags = 0; + _sceneAnimMovie[i] = new WSAMovie_v2(this); + assert(_sceneAnimMovie[i]); + } + + _screen->_curPage = 0; + + _talkObjectList = new TalkObject[88]; + memset(_talkObjectList, 0, sizeof(TalkObject)*88); + for (int i = 0; i < 88; ++i) + _talkObjectList[i].sceneId = 0xFF; + + _gfxBackUpRect = new uint8[_screen->getRectSize(32, 32)]; + initItemList(50); + resetItemList(); + + loadShadowShape(); + loadExtrasShapes(); + _characterShapeFile = 0; + loadCharacterShapes(_characterShapeFile); + updateMalcolmShapes(); + initMainButtonList(true); + loadButtonShapes(); + loadInterfaceShapes(); + + _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); + _paletteOverlay = new uint8[256]; + _screen->generateOverlay(_screen->getPalette(0), _paletteOverlay, 0xF0, 0x19); + + loadInterface(); + + clearAnimObjects(); + + _scoreMax = 0; + for (int i = 0; i < _scoreTableSize; ++i) { + if (_scoreTable[i] > 0) + _scoreMax += _scoreTable[i]; + } + + memset(_newSceneDlgState, 0, sizeof(_newSceneDlgState)); + memset(_conversationState, -1, sizeof(_conversationState)); + + _sceneList = new SceneDesc[98]; + assert(_sceneList); + memset(_sceneList, 0, sizeof(SceneDesc)*98); + _sceneListSize = 98; + + runStartupScript(1, 0); + _res->exists("MOODOMTR.WSA", true); + _invWsa = new WSAMovie_v2(this); + assert(_invWsa); + _invWsa->open("MOODOMTR.WSA", 1, 0); + _invWsaFrame = 6; + saveGameStateIntern(0, "New Game", 0); + if (_gameToLoad == -1) + enterNewScene(_mainCharacter.sceneId, _mainCharacter.facing, 0, 0, 1); + else + loadGameStateCheck(_gameToLoad); + + if (_menuDirectlyToLoad) + (*_mainButtonData[0].buttonCallback)(&_mainButtonData[0]); + + _screen->updateScreen(); + _screen->showMouse(); + + setNextIdleAnimTimer(); + setWalkspeed(_configWalkspeed); +} + +void KyraEngine_MR::loadCostPal() { + _res->exists("_COSTPAL.DAT", true); + uint32 size = 0; + _costPalBuffer = _res->fileData("_COSTPAL.DAT", &size); + assert(_costPalBuffer); + assert(size == 864); +} + +void KyraEngine_MR::loadShadowShape() { + _screen->loadBitmap("SHADOW.CSH", 3, 3, 0); + addShapeToPool(_screen->getCPagePtr(3), 421, 0); +} + +void KyraEngine_MR::loadExtrasShapes() { + _screen->loadBitmap("EXTRAS.CSH", 3, 3, 0); + for (int i = 0; i < 20; ++i) + addShapeToPool(_screen->getCPagePtr(3), i+433, i); + addShapeToPool(_screen->getCPagePtr(3), 453, 20); + addShapeToPool(_screen->getCPagePtr(3), 454, 21); +} + +void KyraEngine_MR::loadInterfaceShapes() { + _screen->loadBitmap("INTRFACE.CSH", 3, 3, 0); + for (int i = 422; i <= 432; ++i) + addShapeToPool(_screen->getCPagePtr(3), i, i-422); +} + +void KyraEngine_MR::loadInterface() { + _screen->loadBitmap("INTRFACE.CPS", 3, 3, 0); + memcpy(_interface, _screen->getCPagePtr(3), 17920); + memcpy(_interfaceCommandLine, _screen->getCPagePtr(3), 3840); +} + +void KyraEngine_MR::initItems() { + _screen->loadBitmap("ITEMS.CSH", 3, 3, 0); + + for (int i = 248; i <= 319; ++i) + addShapeToPool(_screen->getCPagePtr(3), i, i-248); + + _screen->loadBitmap("ITEMS2.CSH", 3, 3, 0); + + for (int i = 320; i <= 397; ++i) + addShapeToPool(_screen->getCPagePtr(3), i, i-320); + + uint32 size = 0; + uint8 *itemsDat = _res->fileData("_ITEMS.DAT", &size); + + assert(size >= 72+144); + + memcpy(_itemBuffer1, itemsDat , 72); + memcpy(_itemBuffer2, itemsDat+72, 144); + + delete[] itemsDat; + + _screen->_curPage = 0; +} + +void KyraEngine_MR::runStartupScript(int script, int unk1) { + EMCState state; + EMCData data; + memset(&state, 0, sizeof(state)); + memset(&data, 0, sizeof(data)); + char filename[13]; + strcpy(filename, "_START0X.EMC"); + filename[7] = (script % 10) + '0'; + + _emc->load(filename, &data, &_opcodes); + _emc->init(&state, &data); + _emc->start(&state, 0); + state.regs[6] = unk1; + + while (_emc->isValid(&state)) + _emc->run(&state); + + _emc->unload(&data); +} + +void KyraEngine_MR::openTalkFile(int file) { + char talkFilename[16]; + + if (file == 0) { + strcpy(talkFilename, "ANYTALK.TLK"); + } else { + if (_currentTalkFile > 0) { + sprintf(talkFilename, "CH%dTALK.TLK", _currentTalkFile); + _res->unloadPakFile(talkFilename); + } + sprintf(talkFilename, "CH%dTALK.TLK", file); + } + + _currentTalkFile = file; + if (!_res->loadPakFile(talkFilename)) { + if (speechEnabled()) { + warning("Couldn't load voice file '%s', falling back to text only mode", talkFilename); + _configVoice = 0; + + // Sync the config manager with the new settings + writeSettings(); + } + } +} + +#pragma mark - + +void KyraEngine_MR::loadCharacterShapes(int newShapes) { + static const uint8 numberOffset[] = { 3, 3, 4, 4, 3, 3 }; + static const uint8 startShape[] = { 0x32, 0x58, 0x78, 0x98, 0xB8, 0xD8 }; + static const uint8 endShape[] = { 0x57, 0x77, 0x97, 0xB7, 0xD7, 0xF7 }; + static const char *const filenames[] = { + "MSW##.SHP", + "MTA##.SHP", + "MTFL##.SHP", + "MTFR##.SHP", + "MTL##.SHP", + "MTR##.SHP" + }; + + for (int i = 50; i <= 247; ++i) { + if (i == 87) + continue; + + ShapeMap::iterator iter = _gameShapes.find(i); + if (iter != _gameShapes.end()) { + delete[] iter->_value; + iter->_value = 0; + } + } + + const char lowNum = (newShapes % 10) + '0'; + const char highNum = (newShapes / 10) + '0'; + + for (int i = 0; i < 6; ++i) { + char filename[16]; + strcpy(filename, filenames[i]); + filename[numberOffset[i]+0] = highNum; + filename[numberOffset[i]+1] = lowNum; + _res->exists(filename, true); + _res->loadFileToBuf(filename, _screenBuffer, 64000); + for (int j = startShape[i]; j <= endShape[i]; ++j) { + if (j == 87) + continue; + addShapeToPool(_screenBuffer, j, j-startShape[i]); + } + } + + _characterShapeFile = newShapes; + updateMalcolmShapes(); +} + +void KyraEngine_MR::updateMalcolmShapes() { + assert(_characterShapeFile >= 0 && _characterShapeFile < _shapeDescsSize); + _malcolmShapeXOffset = _shapeDescs[_characterShapeFile].xOffset; + _malcolmShapeYOffset = _shapeDescs[_characterShapeFile].yOffset; + _animObjects[0].width = _shapeDescs[_characterShapeFile].width; + _animObjects[0].height = _shapeDescs[_characterShapeFile].height; +} + +#pragma mark - + +int KyraEngine_MR::getCharacterWalkspeed() const { + return _mainCharacter.walkspeed; +} + +void KyraEngine_MR::updateCharAnimFrame(int *table) { + ++_mainCharacter.animFrame; + int facing = _mainCharacter.facing; + + if (table) { + if (table[0] != table[-1] && table[1] == table[-1]) { + facing = getOppositeFacingDirection(table[-1]); + table[0] = table[-1]; + } + } + + if (facing) { + if (facing == 7 || facing == 1) { + if (_characterAnimTable[0] > 2) + facing = 0; + memset(_characterAnimTable, 0, sizeof(_characterAnimTable)); + } else if (facing == 4) { + ++_characterAnimTable[1]; + } else if (facing == 5 || facing == 3) { + if (_characterAnimTable[1] > 2) + facing = 4; + memset(_characterAnimTable, 0, sizeof(_characterAnimTable)); + } + } else { + ++_characterAnimTable[0]; + } + + switch (facing) { + case 0: + if (_mainCharacter.animFrame < 79 || _mainCharacter.animFrame > 86) + _mainCharacter.animFrame = 79; + break; + + case 1: case 2: case 3: + if (_mainCharacter.animFrame < 71 || _mainCharacter.animFrame > 78) + _mainCharacter.animFrame = 71; + break; + + case 4: + if (_mainCharacter.animFrame < 55 || _mainCharacter.animFrame > 62) + _mainCharacter.animFrame = 55; + break; + + case 5: case 6: case 7: + if (_mainCharacter.animFrame < 63 || _mainCharacter.animFrame > 70) + _mainCharacter.animFrame = 63; + break; + + default: + break; + } + + updateCharacterAnim(0); +} + +void KyraEngine_MR::updateCharPal(int unk1) { + int layer = _screen->getLayer(_mainCharacter.x1, _mainCharacter.y1) - 1; + const uint8 *src = _costPalBuffer + _characterShapeFile * 72; + Palette &dst = _screen->getPalette(0); + const int8 *sceneDatPal = &_sceneDatPalette[layer * 3]; + + if (layer != _lastCharPalLayer && unk1) { + for (int i = 144; i < 168; ++i) { + for (int j = 0; j < 3; ++j) { + uint8 col = dst[i * 3 + j]; + int subCol = src[(i - 144) * 3 + j] + sceneDatPal[j]; + subCol = CLIP(subCol, 0, 63); + subCol = (col - subCol) / 2; + dst[i * 3 + j] -= subCol; + } + } + + _charPalUpdate = true; + _screen->setScreenPalette(_screen->getPalette(0)); + _lastCharPalLayer = layer; + } else if (_charPalUpdate || !unk1) { + dst.copy(_costPalBuffer, _characterShapeFile * 24, 24, 144); + + for (int i = 144; i < 168; ++i) { + for (int j = 0; j < 3; ++j) { + int col = dst[i * 3 + j] + sceneDatPal[j]; + dst[i * 3 + j] = CLIP(col, 0, 63); + } + } + + _screen->setScreenPalette(_screen->getPalette(0)); + _charPalUpdate = false; + } +} + +bool KyraEngine_MR::checkCharCollision(int x, int y) { + int scale = getScale(_mainCharacter.x1, _mainCharacter.y1); + int width = (scale * 37) >> 8; + int height = (scale * 76) >> 8; + + int x1 = _mainCharacter.x1 - width/2; + int x2 = _mainCharacter.x1 + width/2; + int y1 = _mainCharacter.y1 - height; + int y2 = _mainCharacter.y1; + + if (x >= x1 && x <= x2 && y >= y1 && y <= y2) + return true; + return false; +} + +#pragma mark - + +void KyraEngine_MR::runLoop() { + // Initialize debugger since how it should be fully usable + _debugger->initialize(); + + _eventList.clear(); + + _runFlag = true; + while (_runFlag && !shouldQuit()) { + if (_deathHandler >= 0) { + removeHandItem(); + delay(5); + _drawNoShapeFlag = 0; + _gui->optionsButton(0); + _deathHandler = -1; + + if (!_runFlag || shouldQuit()) + break; + } + + if (_system->getMillis() >= _nextIdleAnim) + showIdleAnim(); + + int inputFlag = checkInput(_mainButtonList, true); + removeInputTop(); + + update(); + _timer->update(); + + if (inputFlag == 198 || inputFlag == 199) { + _savedMouseState = _mouseState; + Common::Point mouse = getMousePos(); + handleInput(mouse.x, mouse.y); + } + + _system->delayMillis(10); + } +} + +void KyraEngine_MR::handleInput(int x, int y) { + if (_inventoryState) + return; + setNextIdleAnimTimer(); + + if (_unk5) { + _unk5 = 0; + return; + } + + if (!_screen->isMouseVisible()) + return; + + if (_savedMouseState == -3) { + snd_playSoundEffect(0x0D, 0x80); + return; + } + + setNextIdleAnimTimer(); + + int skip = 0; + + if (checkCharCollision(x, y) && _savedMouseState >= -1 && runSceneScript2()) { + return; + } else if (_itemInHand != 27 && pickUpItem(x, y, 1)) { + return; + } else if (checkItemCollision(x, y) == -1) { + resetGameFlag(1); + skip = runSceneScript1(x, y); + + if (queryGameFlag(1)) { + resetGameFlag(1); + return; + } else if (_unk5) { + _unk5 = 0; + return; + } + } + + if (_deathHandler >= 0) + skip = 1; + + if (skip) + return; + + if (checkCharCollision(x, y)) { + if (runSceneScript2()) + return; + } else if (_itemInHand >= 0 && _savedMouseState >= 0) { + if (_itemInHand == 27) { + makeCharFacingMouse(); + } else if (y <= 187) { + if (_itemInHand == 43) + removeHandItem(); + else + dropItem(0, _itemInHand, x, y, 1); + } + return; + } else if (_savedMouseState == -3) { + return; + } else { + if (y > 187 && _savedMouseState > -4) + return; + if (_unk5) { + _unk5 = 0; + return; + } + } + + inputSceneChange(x, y, 1, 1); +} + +int KyraEngine_MR::inputSceneChange(int x, int y, int unk1, int unk2) { + uint16 curScene = _mainCharacter.sceneId; + _pathfinderFlag = 15; + + if (!_unkHandleSceneChangeFlag) { + if (_savedMouseState == -4) { + if (_sceneList[curScene].exit4 != 0xFFFF) { + x = 4; + y = _sceneEnterY4; + _pathfinderFlag = 7; + } + } else if (_savedMouseState == -6) { + if (_sceneList[curScene].exit2 != 0xFFFF) { + x = 316; + y = _sceneEnterY2; + _pathfinderFlag = 7; + } + } else if (_savedMouseState == -7) { + if (_sceneList[curScene].exit1 != 0xFFFF) { + x = _sceneEnterX1; + y = _sceneEnterY1 - 2; + _pathfinderFlag = 14; + } + } else if (_savedMouseState == -5) { + if (_sceneList[curScene].exit3 != 0xFFFF) { + x = _sceneEnterX3; + y = 191; + _pathfinderFlag = 11; + } + } + } + + if (ABS(_mainCharacter.x1 - x) < 4 && ABS(_mainCharacter.y1 - y) < 2) { + _pathfinderFlag = 0; + return 0; + } + + int x1 = _mainCharacter.x1 & (~3); + int y1 = _mainCharacter.y1 & (~1); + x &= ~3; + y &= ~1; + + int size = findWay(x1, y1, x, y, _movFacingTable, 600); + _pathfinderFlag = 0; + + if (!size || size == 0x7D00) + return 0; + + return trySceneChange(_movFacingTable, unk1, unk2); +} + +void KyraEngine_MR::update() { + updateInput(); + + refreshAnimObjectsIfNeed(); + updateMouse(); + updateSpecialSceneScripts(); + updateCommandLine(); + updateItemAnimations(); + + _screen->updateScreen(); +} + +void KyraEngine_MR::updateWithText() { + updateInput(); + + updateMouse(); + updateItemAnimations(); + updateSpecialSceneScripts(); + updateCommandLine(); + + restorePage3(); + drawAnimObjects(); + if (_chatTextEnabled && _chatText) { + int curPage = _screen->_curPage; + _screen->_curPage = 2; + objectChatPrintText(_chatText, _chatObject); + _screen->_curPage = curPage; + } + refreshAnimObjects(0); + + _screen->updateScreen(); +} + +void KyraEngine_MR::updateMouse() { + int shape = 0, offsetX = 0, offsetY = 0; + Common::Point mouse = getMousePos(); + bool hasItemCollision = checkItemCollision(mouse.x, mouse.y) != -1; + + if (mouse.y > 187) { + bool setItemCursor = false; + if (_mouseState == -6) { + if (mouse.x < 311) + setItemCursor = true; + } else if (_mouseState == -5) { + if (mouse.x < _sceneMinX || mouse.x > _sceneMaxX) + setItemCursor = true; + } else if (_mouseState == -4) { + if (mouse.x > 8) + setItemCursor = true; + } + + if (setItemCursor) { + setItemMouseCursor(); + return; + } + } + + if (_inventoryState) { + if (mouse.y >= 144) + return; + hideInventory(); + } + + if (hasItemCollision && _mouseState < -1 && _itemInHand < 0) { + _mouseState = kItemNone; + _itemInHand = kItemNone; + _screen->setMouseCursor(0, 0, _gameShapes[0]); + } + + int type = 0; + if (mouse.y <= 199) { + if (mouse.x <= 8) { + if (_sceneExit4 != 0xFFFF) { + type = -4; + shape = 4; + offsetX = 0; + offsetY = 0; + } + } else if (mouse.x >= 311) { + if (_sceneExit2 != 0xFFFF) { + type = -6; + shape = 2; + offsetX = 13; + offsetY = 8; + } + } else if (mouse.y >= 171) { + if (_sceneExit3 != 0xFFFF) { + if (mouse.x >= _sceneMinX && mouse.x <= _sceneMaxX) { + type = -5; + shape = 3; + offsetX = 8; + offsetY = 13; + } + } + } else if (mouse.y <= 8) { + if (_sceneExit1 != 0xFFFF) { + type = -7; + shape = 1; + offsetX = 8; + offsetY = 0; + } + } + } + + for (int i = 0; i < _specialExitCount; ++i) { + if (checkSpecialSceneExit(i, mouse.x, mouse.y)) { + switch (_specialExitTable[20+i]) { + case 0: + type = -7; + shape = 1; + offsetX = 8; + offsetY = 0; + break; + + case 2: + type = -6; + shape = 2; + offsetX = 13; + offsetY = 8; + break; + + case 4: + type = -5; + shape = 3; + offsetX = 8; + offsetY = 13; + break; + + case 6: + type = -4; + shape = 4; + offsetX = 0; + offsetY = 8; + break; + + default: + break; + } + } + } + + if (type != 0 && type != _mouseState && !hasItemCollision) { + _mouseState = type; + _screen->setMouseCursor(offsetX, offsetY, _gameShapes[shape]); + } else if (type == 0 && _mouseState != _itemInHand && mouse.x > 8 && mouse.x < 311 && mouse.y < 171 && mouse.y > 8) { + setItemMouseCursor(); + } else if (mouse.y > 187 && _mouseState > -4 && type == 0 && !_inventoryState) { + showInventory(); + } +} + +#pragma mark - + +void KyraEngine_MR::makeCharFacingMouse() { + if (_mainCharacter.x1 > _mouseX) + _mainCharacter.facing = 5; + else + _mainCharacter.facing = 3; + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); +} + +#pragma mark - + +int KyraEngine_MR::getDrawLayer(int x, int y) { + int layer = _screen->getLayer(x, y) - 1; + layer = _sceneDatLayerTable[layer]; + return MAX(0, MIN(layer, 6)); +} + +int KyraEngine_MR::getScale(int x, int y) { + return _scaleTable[_screen->getLayer(x, y) - 1]; +} + +#pragma mark - + +void KyraEngine_MR::backUpGfxRect32x32(int x, int y) { + _screen->copyRegionToBuffer(_screen->_curPage, x, y, 32, 32, _gfxBackUpRect); +} + +void KyraEngine_MR::restoreGfxRect32x32(int x, int y) { + _screen->copyBlockToPage(_screen->_curPage, x, y, 32, 32, _gfxBackUpRect); +} + +#pragma mark - + +int KyraEngine_MR::loadLanguageFile(const char *file, uint8 *&buffer) { + delete[] buffer; + buffer = 0; + + uint32 size = 0; + Common::String nBuf = file; + nBuf += _languageExtension[_lang]; + buffer = _res->fileData(nBuf.c_str(), &size); + + return buffer ? size : 0; +} + +uint8 *KyraEngine_MR::getTableEntry(uint8 *buffer, int id) { + uint16 tableEntries = READ_LE_UINT16(buffer); + const uint16 *indexTable = (const uint16 *)(buffer + 2); + const uint16 *offsetTable = indexTable + tableEntries; + + int num = 0; + while (id != READ_LE_UINT16(indexTable)) { + ++indexTable; + ++num; + } + + return buffer + READ_LE_UINT16(offsetTable + num); +} + +void KyraEngine_MR::getTableEntry(Common::SeekableReadStream *stream, int id, char *dst) { + stream->seek(0, SEEK_SET); + uint16 tableEntries = stream->readUint16LE(); + + int num = 0; + while (id != stream->readUint16LE()) + ++num; + + stream->seek(2+tableEntries*2+num*2, SEEK_SET); + stream->seek(stream->readUint16LE(), SEEK_SET); + char c = 0; + while ((c = stream->readByte()) != 0) + *dst++ = c; + *dst = 0; +} + +#pragma mark - + +bool KyraEngine_MR::talkObjectsInCurScene() { + for (int i = 0; i < 88; ++i) { + if (_talkObjectList[i].sceneId == _mainCharacter.sceneId) + return true; + } + + return false; +} + +#pragma mark - + +bool KyraEngine_MR::updateScore(int scoreId, int strId) { + int scoreIndex = (scoreId >> 3); + int scoreBit = scoreId & 7; + if ((_scoreFlagTable[scoreIndex] & (1 << scoreBit)) != 0) + return false; + + setNextIdleAnimTimer(); + _scoreFlagTable[scoreIndex] |= (1 << scoreBit); + + strcpy(_stringBuffer, (const char *)getTableEntry(_scoreFile, strId)); + strcat(_stringBuffer, ": "); + + assert(scoreId < _scoreTableSize); + + int count = _scoreTable[scoreId]; + if (count > 0) + scoreIncrease(count, _stringBuffer); + + setNextIdleAnimTimer(); + return true; +} + +void KyraEngine_MR::scoreIncrease(int count, const char *str) { + int drawOld = 1; + _screen->hideMouse(); + + showMessage(str, 0xFF, 0xF0); + const int x = getScoreX(str); + + for (int i = 0; i < count; ++i) { + int oldScore = _score; + int newScore = ++_score; + + if (newScore > _scoreMax) { + _score = _scoreMax; + break; + } + + drawScoreCounting(oldScore, newScore, drawOld, x); + if (_inventoryState) + drawScore(0, 215, 191); + _screen->updateScreen(); + delay(20, true); + + snd_playSoundEffect(0x0E, 0xC8); + drawOld = 0; + } + + _screen->showMouse(); +} + +#pragma mark - + +void KyraEngine_MR::changeChapter(int newChapter, int sceneId, int malcolmShapes, int facing) { + resetItemList(); + + _currentChapter = newChapter; + runStartupScript(newChapter, 0); + _mainCharacter.dlgIndex = 0; + + _malcolmsMood = 1; + memset(_newSceneDlgState, 0, sizeof(_newSceneDlgState)); + + if (malcolmShapes >= 0) + loadCharacterShapes(malcolmShapes); + + enterNewScene(sceneId, facing, 0, 0, 0); +} + +#pragma mark - + +bool KyraEngine_MR::skipFlag() const { + if (!_configSkip) + return false; + return KyraEngine_v2::skipFlag(); +} + +void KyraEngine_MR::resetSkipFlag(bool removeEvent) { + if (!_configSkip) { + if (removeEvent) + _eventList.clear(); + return; + } + KyraEngine_v2::resetSkipFlag(removeEvent); +} + +#pragma mark - + +void KyraEngine_MR::registerDefaultSettings() { + KyraEngine_v1::registerDefaultSettings(); + + // Most settings already have sensible defaults. This one, however, is + // specific to the Kyra engine. + ConfMan.registerDefault("walkspeed", 5); + ConfMan.registerDefault("studio_audience", true); + ConfMan.registerDefault("skip_support", true); + ConfMan.registerDefault("helium_mode", false); + // 0 - best, 1 - mid, 2 - low + ConfMan.registerDefault("video_quality", 0); +} + +void KyraEngine_MR::writeSettings() { + switch (_lang) { + case 1: + _flags.lang = Common::FR_FRA; + break; + + case 2: + _flags.lang = Common::DE_DEU; + break; + + case 0: + default: + _flags.lang = Common::EN_ANY; + } + + if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG) + _flags.lang = _flags.fanLang; + + ConfMan.set("language", Common::getLanguageCode(_flags.lang)); + + ConfMan.setBool("studio_audience", _configStudio); + ConfMan.setBool("skip_support", _configSkip); + ConfMan.setBool("helium_mode", _configHelium); + + KyraEngine_v1::writeSettings(); +} + +void KyraEngine_MR::readSettings() { + KyraEngine_v2::readSettings(); + + _configStudio = ConfMan.getBool("studio_audience"); + _configSkip = ConfMan.getBool("skip_support"); + _configHelium = ConfMan.getBool("helium_mode"); + _configVQAQuality = CLIP(ConfMan.getInt("video_quality"), 0, 2); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/kyra_mr.h b/engines/kyra/engine/kyra_mr.h new file mode 100644 index 0000000000..83c97ebad9 --- /dev/null +++ b/engines/kyra/engine/kyra_mr.h @@ -0,0 +1,670 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_KYRA_MR_H +#define KYRA_KYRA_MR_H + +#include "kyra/engine/kyra_v2.h" +#include "kyra/graphics/screen_mr.h" +#include "kyra/script/script.h" +#include "kyra/gui/gui_mr.h" + +#include "common/hashmap.h" +#include "common/list.h" + +namespace Kyra { + +class SoundDigital; +class Screen_MR; +class MainMenu; +class WSAMovie_v2; +class TextDisplayer_MR; +struct Button; + +class KyraEngine_MR : public KyraEngine_v2 { +friend class TextDisplayer_MR; +friend class GUI_MR; +public: + KyraEngine_MR(OSystem *system, const GameFlags &flags); + ~KyraEngine_MR(); + + // Regarding pausing of the engine: + // Idle animation time, item animations and album animations should be taken + // care of, but since those would just produce minor glitches it's not that + // important. + + Screen *screen() { return _screen; } + Screen_v2 *screen_v2() const { return _screen; } + GUI *gui() const { return _gui; } + SoundDigital *soundDigital() { return _soundDigital; } + int language() const { return _lang; } + bool heliumMode() const { return _configHelium; } + + Common::Error go(); + + void playVQA(const char *name); + +private: + static const EngineDesc _mrEngineDesc; + + // config + bool _configStudio; + bool _configSkip; + bool _configHelium; + int _configVQAQuality; + + void registerDefaultSettings(); + void writeSettings(); + void readSettings(); + + void initStaticResource(); + + // -- + Screen_MR *_screen; + SoundDigital *_soundDigital; + + Common::Error init(); + + void preinit(); + void startup(); + void runStartupScript(int script, int unk1); + + void setupOpcodeTable(); + + // input + bool skipFlag() const; + void resetSkipFlag(bool removeEvent = true); + + // run + bool _menuDirectlyToLoad; + + void runLoop(); + void handleInput(int x, int y); + int inputSceneChange(int x, int y, int unk1, int unk2); + + void update(); + void updateWithText(); + void updateMouse(); + + // sound specific +private: + void playMenuAudioFile(); + + int _musicSoundChannel; + int _fadeOutMusicChannel; + const char *_menuAudioFile; + + const char *const *_soundList; + int _soundListSize; + + void snd_playWanderScoreViaMap(int track, int force); + void stopMusicTrack(); + + void fadeOutMusic(int ticks); + + void snd_playSoundEffect(int item, int volume); + + const uint8 *_sfxFileMap; + int _sfxFileMapSize; + const char *const *_sfxFileList; + int _sfxFileListSize; + + int _voiceSoundChannel; + + void playVoice(int high, int low); + void snd_playVoiceFile(int file); + bool snd_voiceIsPlaying(); + void snd_stopVoice(); + + int _curStudioSFX; + void playStudioSFX(const char *str); + + // gui + GUI_MR *_gui; + + Button *_mainButtonData; + Button *_mainButtonList; + bool _mainButtonListInitialized; + void initMainButtonList(bool disable); + + bool _enableInventory; + int buttonInventory(Button *button); + int buttonMoodChange(Button *button); + int buttonShowScore(Button *button); + int buttonJesterStaff(Button *button); + + void loadButtonShapes(); + int callbackButton1(Button *button); + int callbackButton2(Button *button); + int callbackButton3(Button *button); + + // -> main menu + void initMainMenu(); + void uninitMainMenu(); + + MainMenu *_menu; + WSAMovie_v2 *_menuAnim; + + // timer + void setupTimers(); + + void setWalkspeed(uint8); + void setCommandLineRestoreTimer(int secs); + + void timerRestoreCommandLine(int arg); + void timerRunSceneScript7(int arg); + void timerFleaDeath(int arg); + + uint32 _nextIdleAnim; + void setNextIdleAnimTimer(); + + // pathfinder + bool lineIsPassable(int x, int y); + +private: + // main menu + const char *const *_mainMenuStrings; + int _mainMenuStringsSize; + + static const char *const _mainMenuSpanishFan[]; + static const char *const _mainMenuItalianFan[]; + + // animator + uint8 *_gamePlayBuffer; + void restorePage3(); + + void clearAnimObjects(); + + void animSetupPaletteEntry(AnimObj *anim); + + void drawAnimObjects(); + void drawSceneAnimObject(AnimObj *obj, int x, int y, int drawLayer); + void drawCharacterAnimObject(AnimObj *obj, int x, int y, int drawLayer); + + void refreshAnimObjects(int force); + + bool _loadingState; + void updateItemAnimations(); + void updateCharacterAnim(int charId); + + void updateSceneAnim(int anim, int newFrame); + void setupSceneAnimObject(int anim, uint16 flags, int x, int y, int x2, int y2, int w, int h, int unk10, int specialSize, int unk14, int shape, const char *filename); + void removeSceneAnimObject(int anim, int refresh); + + int _charBackUpWidth2, _charBackUpHeight2; + int _charBackUpWidth, _charBackUpHeight; + + void setCharacterAnimDim(int w, int h); + void resetCharacterAnimDim(); + + bool _nextIdleType; + void showIdleAnim(); + + const ItemAnimDefinition *_itemAnimDefinition; + ActiveItemAnim _activeItemAnim[10]; + int _nextAnimItem; + + // interface + uint8 *_interface; + uint8 *_interfaceCommandLine; + + void loadInterfaceShapes(); + void loadInterface(); + + void showMessage(const char *string, uint8 c0, uint8 c1); + void showMessageFromCCode(int string, uint8 c0, int); + void updateItemCommand(Item item, int str, uint8 c0); + + void updateCommandLine(); + void restoreCommandLine(); + void updateCLState(); + + int _commandLineY; + const char *_shownMessage; + bool _restoreCommandLine; + bool _inventoryState; + int _inventoryScrollSpeed; + + void showInventory(); + void hideInventory(); + + void drawMalcolmsMoodText(); + void drawMalcolmsMoodPointer(int frame, int page); + void drawJestersStaff(int type, int page); + + void drawScore(int page, int x, int y); + void drawScoreCounting(int oldScore, int newScore, int drawOld, const int x); + int getScoreX(const char *str); + + static const uint8 _inventoryX[]; + static const uint8 _inventoryY[]; + void redrawInventory(int page); + void clearInventorySlot(int slot, int page); + void drawInventorySlot(int page, Item item, int slot); + + WSAMovie_v2 *_invWsa; + int _invWsaFrame; + + // localization + uint8 *_scoreFile; + uint8 *_cCodeFile; + uint8 *_scenesFile; + uint8 *_itemFile; + uint8 *_optionsFile; + uint8 *_actorFile; + uint32 _actorFileSize; + uint8 *_sceneStrings; + + uint8 *getTableEntry(uint8 *buffer, int id); + void getTableEntry(Common::SeekableReadStream *stream, int id, char *dst); + + // items + int8 *_itemBuffer1; + int8 *_itemBuffer2; + + static const Item _trashItemList[]; + void removeTrashItems(); + + void initItems(); + + int checkItemCollision(int x, int y); + + bool dropItem(int unk1, Item item, int x, int y, int unk2); + bool processItemDrop(uint16 sceneId, Item item, int x, int y, int unk1, int unk2); + void itemDropDown(int startX, int startY, int dstX, int dstY, int itemSlot, Item item, int remove); + void exchangeMouseItem(int itemPos, int runScript); + bool pickUpItem(int x, int y, int runScript); + + bool isDropable(int x, int y); + + const uint8 *_itemMagicTable; + bool itemListMagic(Item handItem, int itemSlot); + bool itemInventoryMagic(Item handItem, int invSlot); + + const uint8 *_itemStringMap; + int _itemStringMapSize; + static const uint8 _itemStringPickUp[]; + static const uint8 _itemStringDrop[]; + static const uint8 _itemStringInv[]; + + int getItemCommandStringPickUp(uint16 item); + int getItemCommandStringDrop(uint16 item); + int getItemCommandStringInv(uint16 item); + + // -> hand item + void setItemMouseCursor(); + void setMouseCursor(Item item); + + // shapes + void initMouseShapes(); + + void loadCharacterShapes(int newShapes); + void updateMalcolmShapes(); + + int _malcolmShapeXOffset, _malcolmShapeYOffset; + + struct ShapeDesc { + uint8 width, height; + int8 xOffset, yOffset; + }; + static const ShapeDesc _shapeDescs[]; + static const int _shapeDescsSize; + + // scene animation + uint8 *_sceneShapes[20]; + + void freeSceneShapes(); + + // voice + int _currentTalkFile; + void openTalkFile(int file); + + // scene + bool _noScriptEnter; + void enterNewScene(uint16 scene, int facing, int unk1, int unk2, int unk3); + void enterNewSceneUnk1(int facing, int unk1, int unk2); + void enterNewSceneUnk2(int unk1); + int _enterNewSceneLock; + + void unloadScene(); + + void loadScenePal(); + void loadSceneMsc(); + void initSceneScript(int unk1); + void initSceneAnims(int unk1); + void initSceneScreen(int unk1); + + int runSceneScript1(int x, int y); + int runSceneScript2(); + bool _noStartupChat; + void runSceneScript4(int unk1); + void runSceneScript8(); + + int _sceneMinX, _sceneMaxX; + int _maskPageMinY, _maskPageMaxY; + + int trySceneChange(int *moveTable, int unk1, int unk2); + int checkSceneChange(); + + int8 _sceneDatPalette[45]; + int8 _sceneDatLayerTable[15]; + struct SceneShapeDesc { + // the original saves those variables, we don't, since + // they are just needed on scene load + /*int x, y; + int w, h;*/ + int drawX, drawY; + }; + SceneShapeDesc _sceneShapeDescs[20]; + + int getDrawLayer(int x, int y); + + int getScale(int x, int y); + int _scaleTable[15]; + + // character + int getCharacterWalkspeed() const; + void updateCharAnimFrame(int *table); + int8 _characterAnimTable[2]; + static const uint8 _characterFrameTable[]; + + void updateCharPal(int unk1); + int _lastCharPalLayer; + bool _charPalUpdate; + + bool checkCharCollision(int x, int y); + + int _malcolmsMood; + + void makeCharFacingMouse(); + + int findFreeInventorySlot(); + + // talk object + struct TalkObject { + char filename[13]; + int8 sceneAnim; + int8 sceneScript; + int16 x, y; + uint8 color; + uint8 sceneId; + }; + + TalkObject *_talkObjectList; + + bool talkObjectsInCurScene(); + + // chat + int chatGetType(const char *text); + int chatCalcDuration(const char *text); + + void objectChat(const char *text, int object, int vocHigh, int vocLow); + void objectChatInit(const char *text, int object, int vocHigh, int vocLow); + void objectChatPrintText(const char *text, int object); + void objectChatProcess(const char *script); + void objectChatWaitToFinish(); + + void badConscienceChat(const char *str, int vocHigh, int vocLow); + void badConscienceChatWaitToFinish(); + + void goodConscienceChat(const char *str, int vocHigh, int vocLow); + void goodConscienceChatWaitToFinish(); + + bool _albumChatActive; + void albumChat(const char *str, int vocHigh, int vocLow); + void albumChatInit(const char *str, int object, int vocHigh, int vocLow); + void albumChatWaitToFinish(); + + void malcolmSceneStartupChat(); + + byte _newSceneDlgState[40]; + int8 _conversationState[30][30]; + bool _chatAltFlag; + void setDlgIndex(int index); + void updateDlgIndex(); + + Common::SeekableReadStream *_cnvFile; + Common::SeekableReadStream *_dlgBuffer; + int _curDlgChapter, _curDlgIndex, _curDlgLang; + void updateDlgBuffer(); + void loadDlgHeader(int &vocHighBase, int &vocHighIndex, int &index1, int &index2); + + static const uint8 _vocHighTable[]; + bool _isStartupDialog; + void processDialog(int vocHighIndex, int vocHighBase, int funcNum); + + EMCData _dialogScriptData; + EMCState _dialogScriptState; + int _dialogSceneAnim; + int _dialogSceneScript; + int _dialogScriptFuncStart, _dialogScriptFuncProc, _dialogScriptFuncEnd; + + void dialogStartScript(int object, int funcNum); + void dialogEndScript(int object); + + void npcChatSequence(const char *str, int object, int vocHigh, int vocLow); + + Common::Array _opcodesDialog; + + int o3d_updateAnim(EMCState *script); + int o3d_delay(EMCState *script); + + void randomSceneChat(); + void doDialog(int dlgIndex, int funcNum); + + // conscience + bool _badConscienceShown; + int _badConscienceAnim; + bool _badConsciencePosition; + + static const uint8 _badConscienceFrameTable[]; + + void showBadConscience(); + void hideBadConscience(); + + bool _goodConscienceShown; + int _goodConscienceAnim; + bool _goodConsciencePosition; + + static const uint8 _goodConscienceFrameTable[]; + + void showGoodConscience(); + void hideGoodConscience(); + + // special script code + bool _useFrameTable; + + int o3a_setCharacterFrame(EMCState *script); + int o3a_playSoundEffect(EMCState *script); + + // special shape code + int initAnimationShapes(uint8 *filedata); + void uninitAnimationShapes(int count, uint8 *filedata); + + // unk + uint8 *_costPalBuffer; + uint8 *_paletteOverlay; + bool _useActorBuffer; + + int _currentChapter; + void changeChapter(int newChapter, int sceneId, int malcolmShapes, int facing); + + static const uint8 _chapterLowestScene[]; + + void loadCostPal(); + void loadShadowShape(); + void loadExtrasShapes(); + + uint8 *_gfxBackUpRect; + void backUpGfxRect32x32(int x, int y); + void restoreGfxRect32x32(int x, int y); + + char *_stringBuffer; + + int _score; + int _scoreMax; + + const uint8 *_scoreTable; + int _scoreTableSize; + + int8 _scoreFlagTable[26]; + bool updateScore(int scoreId, int strId); + void scoreIncrease(int count, const char *str); + + void eelScript(); + + // Album + struct Album { + uint8 *backUpPage; + uint8 *file; + WSAMovie_v2 *wsa; + uint8 *backUpRect; + + struct PageMovie { + WSAMovie_v2 *wsa; + int curFrame; + int maxFrame; + uint32 timer; + }; + + PageMovie leftPage, rightPage; + + int curPage, nextPage; + bool running; + bool isPage14; + } _album; + + static const int8 _albumWSAX[]; + static const int8 _albumWSAY[]; + + void showAlbum(); + + void loadAlbumPage(); + void loadAlbumPageWSA(); + + void printAlbumPageText(); + void printAlbumText(int page, const char *str, int x, int y, uint8 c0); + + void processAlbum(); + + void albumNewPage(); + void albumUpdateAnims(); + void albumAnim1(); + void albumAnim2(); + + void albumBackUpRect(); + void albumRestoreRect(); + void albumUpdateRect(); + + void albumSwitchPages(int oldPage, int newPage, int srcPage); + + int albumNextPage(Button *caller); + int albumPrevPage(Button *caller); + int albumClose(Button *caller); + + // save/load + Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail); + Common::Error loadGameState(int slot); + + // opcodes + int o3_getMalcolmShapes(EMCState *script); + int o3_setCharacterPos(EMCState *script); + int o3_defineObject(EMCState *script); + int o3_refreshCharacter(EMCState *script); + int o3_getMalcolmsMood(EMCState *script); + int o3_getCharacterFrameFromFacing(EMCState *script); + int o3_setCharacterFacing(EMCState *script); + int o3_showSceneFileMessage(EMCState *script); + int o3_setCharacterAnimFrameFromFacing(EMCState *script); + int o3_showBadConscience(EMCState *script); + int o3_hideBadConscience(EMCState *script); + int o3_showAlbum(EMCState *script); + int o3_setInventorySlot(EMCState *script); + int o3_getInventorySlot(EMCState *script); + int o3_addItemToInventory(EMCState *script); + int o3_addItemToCurScene(EMCState *script); + int o3_objectChat(EMCState *script); + int o3_resetInventory(EMCState *script); + int o3_removeInventoryItemInstances(EMCState *script); + int o3_countInventoryItemInstances(EMCState *script); + int o3_npcChatSequence(EMCState *script); + int o3_badConscienceChat(EMCState *script); + int o3_wipeDownMouseItem(EMCState *script); + int o3_setMalcolmsMood(EMCState *script); + int o3_updateScore(EMCState *script); + int o3_makeSecondChanceSave(EMCState *script); + int o3_setSceneFilename(EMCState *script); + int o3_removeItemsFromScene(EMCState *script); + int o3_disguiseMalcolm(EMCState *script); + int o3_drawSceneShape(EMCState *script); + int o3_drawSceneShapeOnPage(EMCState *script); + int o3_checkInRect(EMCState *script); + int o3_updateConversations(EMCState *script); + int o3_removeItemSlot(EMCState *script); + int o3_setSceneDim(EMCState *script); + int o3_setSceneAnimPosAndFrame(EMCState *script); + int o3_removeItemInstances(EMCState *script); + int o3_disableInventory(EMCState *script); + int o3_enableInventory(EMCState *script); + int o3_enterNewScene(EMCState *script); + int o3_switchScene(EMCState *script); + int o3_setMalcolmPos(EMCState *script); + int o3_stopMusic(EMCState *script); + int o3_playSoundEffect(EMCState *script); + int o3_getScore(EMCState *script); + int o3_daggerWarning(EMCState *script); + int o3_blockOutWalkableRegion(EMCState *script); + int o3_showSceneStringsMessage(EMCState *script); + int o3_showGoodConscience(EMCState *script); + int o3_goodConscienceChat(EMCState *script); + int o3_hideGoodConscience(EMCState *script); + int o3_defineSceneAnim(EMCState *script); + int o3_updateSceneAnim(EMCState *script); + int o3_runActorScript(EMCState *script); + int o3_doDialog(EMCState *script); + int o3_setConversationState(EMCState *script); + int o3_getConversationState(EMCState *script); + int o3_changeChapter(EMCState *script); + int o3_countItemInstances(EMCState *script); + int o3_dialogStartScript(EMCState *script); + int o3_dialogEndScript(EMCState *script); + int o3_customChat(EMCState *script); + int o3_customChatFinish(EMCState *script); + int o3_setupSceneAnimObject(EMCState *script); + int o3_removeSceneAnimObject(EMCState *script); + int o3_dummy(EMCState *script); + + // misc + TextDisplayer_MR *_text; + bool _wasPlayingVQA; + + // resource specific +private: + static const char *const _languageExtension[]; + static const int _languageExtensionSize; + + int loadLanguageFile(const char *file, uint8 *&buffer); +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/kyra_rpg.cpp b/engines/kyra/engine/kyra_rpg.cpp new file mode 100644 index 0000000000..3d7a4df208 --- /dev/null +++ b/engines/kyra/engine/kyra_rpg.cpp @@ -0,0 +1,366 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) + +#include "kyra/engine/kyra_rpg.h" +#include "kyra/sound/sound.h" + +#include "common/system.h" + +namespace Kyra { + +KyraRpgEngine::KyraRpgEngine(OSystem *system, const GameFlags &flags) : KyraEngine_v1(system, flags), _numFlyingObjects(_flags.gameID == GI_LOL ? 8 : 10) { + _txt = 0; + _mouseClick = 0; + _preserveEvents = _buttonListChanged = false; + + _sceneXoffset = 0; + _sceneShpDim = 5; + + _activeButtons = 0; + + _currentLevel = 0; + + _vcnBlocks = 0; + _vcfBlocks = 0; + _vcnTransitionMask = 0; + _vcnShift = 0; + _vcnColTable = 0; + _vcnBpp = flags.useHiColorMode ? 2 : 1; + _vmpPtr = 0; + _blockBrightness = _wllVcnOffset = 0; + _blockDrawingBuffer = 0; + _sceneWindowBuffer = 0; + _monsterShapes = _monsterPalettes = 0; + + _doorShapes = 0; + + _levelDecorationProperties = 0; + _levelDecorationData = 0; + _levelDecorationShapes = 0; + _decorationCount = 0; + _mappedDecorationsCount = 0; + memset(_visibleBlockIndex, 0, sizeof(_visibleBlockIndex)); + + _lvlShapeTop = _lvlShapeBottom = _lvlShapeLeftRight = 0; + _levelBlockProperties = 0; + _hasTempDataFlags = 0; + + _wllVmpMap = _specialWallTypes = _wllWallFlags = 0; + _wllShapeMap = 0; + + _sceneDrawVarDown = _sceneDrawVarRight = _sceneDrawVarLeft = _wllProcessFlag = 0; + + _currentBlock = 0; + _currentDirection = 0; + _compassDirection = -1; + _updateFlags = _clickedSpecialFlag = 0; + _sceneDefaultUpdate = 0; + _sceneUpdateRequired = false; + + _flyingObjectsPtr = 0; + _flyingObjectStructSize = sizeof(EoBFlyingObject); + + _clickedShapeXOffs = _clickedShapeYOffs = 0; + + _dscShapeX = 0; + _dscTileIndex = 0; + _dscDoorScaleOffs = 0; + _dscDim1 = 0; + _dscDim2 = 0; + _dscBlockMap = 0; + _dscBlockIndex = 0; + _dscShapeIndex = 0; + _dscDimMap = 0; + _dscDoorShpIndex = 0; + _dscDoorY2 = 0; + _dscDoorFrameY1 = 0; + _dscDoorFrameY2 = 0; + _dscDoorFrameIndex1 = 0; + _dscDoorFrameIndex2 = 0; + + _shpDmX1 = _shpDmX2 = 0; + + memset(_openDoorState, 0, sizeof(_openDoorState)); + memset(_dialogueButtonString, 0, 3 * sizeof(const char *)); + _dialogueButtonPosX = 0; + _dialogueButtonPosY = 0; + _dialogueNumButtons = _dialogueButtonYoffs = _dialogueHighlightedButton = 0; + _currentControlMode = 0; + _specialSceneFlag = 0; + _updateCharNum = -1; + _activeVoiceFileTotalTime = 0; + _updatePortraitSpeechAnimDuration = _resetPortraitAfterSpeechAnim = _needSceneRestore = 0; + _fadeText = false; + + memset(_lvlTempData, 0, sizeof(_lvlTempData)); + + _dialogueField = false; + + _environmentSfx = _environmentSfxVol = _envSfxDistThreshold = 0; + _monsterStepCounter = _monsterStepMode = 0; +} + +KyraRpgEngine::~KyraRpgEngine() { + delete[] _wllVmpMap; + delete[] _wllShapeMap; + delete[] _specialWallTypes; + delete[] _wllWallFlags; + + delete[] _vmpPtr; + delete[] _vcnColTable; + delete[] _vcnBlocks; + delete[] _vcfBlocks; + delete[] _vcnTransitionMask; + delete[] _vcnShift; + delete[] _blockDrawingBuffer; + delete[] _sceneWindowBuffer; + + delete[] _lvlShapeTop; + delete[] _lvlShapeBottom; + delete[] _lvlShapeLeftRight; + + delete[] _doorShapes; + + delete[] _levelDecorationShapes; + delete[] _levelDecorationData; + delete[] _levelDecorationProperties; + delete[] _levelBlockProperties; +} + +Common::Error KyraRpgEngine::init() { + gui_resetButtonList(); + + _levelDecorationProperties = new LevelDecorationProperty[100]; + memset(_levelDecorationProperties, 0, 100 * sizeof(LevelDecorationProperty)); + _levelDecorationShapes = new uint8*[400]; + memset(_levelDecorationShapes, 0, 400 * sizeof(uint8 *)); + _levelBlockProperties = new LevelBlockProperty[1025]; + memset(_levelBlockProperties, 0, 1025 * sizeof(LevelBlockProperty)); + + _wllVmpMap = new uint8[256]; + memset(_wllVmpMap, 0, 256); + _wllShapeMap = new int8[256]; + memset(_wllShapeMap, 0, 256); + _specialWallTypes = new uint8[256]; + memset(_specialWallTypes, 0, 256); + _wllWallFlags = new uint8[256]; + memset(_wllWallFlags, 0, 256); + + _blockDrawingBuffer = new uint16[1320]; + memset(_blockDrawingBuffer, 0, 1320 * sizeof(uint16)); + int windowBufferSize = _flags.useHiColorMode ? 42240 : 21120; + _sceneWindowBuffer = new uint8[windowBufferSize]; + memset(_sceneWindowBuffer, 0, windowBufferSize); + + _lvlShapeTop = new int16[18]; + memset(_lvlShapeTop, 0, 18 * sizeof(int16)); + _lvlShapeBottom = new int16[18]; + memset(_lvlShapeBottom, 0, 18 * sizeof(int16)); + _lvlShapeLeftRight = new int16[36]; + memset(_lvlShapeLeftRight, 0, 36 * sizeof(int16)); + + _vcnColTable = new uint8[128]; + for (int i = 0; i < 128; i++) + _vcnColTable[i] = i & 0x0F; + + _doorShapes = new uint8*[6]; + memset(_doorShapes, 0, 6 * sizeof(uint8 *)); + + initStaticResource(); + + _envSfxDistThreshold = (_flags.gameID == GI_EOB2 || _sound->getSfxType() == Sound::kAdLib || _sound->getSfxType() == Sound::kPCSpkr) ? 15 : 3; + + _dialogueButtonLabelColor1 = guiSettings()->buttons.labelColor1; + _dialogueButtonLabelColor2 = guiSettings()->buttons.labelColor2; + _dialogueButtonWidth = guiSettings()->buttons.width; + + return Common::kNoError; +} + +bool KyraRpgEngine::posWithinRect(int posX, int posY, int x1, int y1, int x2, int y2) { + if (posX < x1 || posX > x2 || posY < y1 || posY > y2) + return false; + return true; +} + +void KyraRpgEngine::drawDialogueButtons() { + int cp = screen()->setCurPage(0); + Screen::FontId of = screen()->setFont((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? Screen::FID_SJIS_FNT : ((_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? Screen::FID_8_FNT : Screen::FID_6_FNT)); + + for (int i = 0; i < _dialogueNumButtons; i++) { + int x = _dialogueButtonPosX[i]; + if (_flags.lang == Common::JA_JPN && _flags.use16ColorMode) { + gui_drawBox(x, ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) & ~7) - 1, 74, 10, 0xEE, 0xCC, -1); + screen()->printText(_dialogueButtonString[i], (x + 37 - (screen()->getTextWidth(_dialogueButtonString[i])) / 2) & ~3, + ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) + 2) & ~7, _dialogueHighlightedButton == i ? 0xC1 : 0xE1, 0); + } else { + int sjisYOffset = (_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? 1 : ((_flags.lang == Common::JA_JPN && (_dialogueButtonString[i][0] & 0x80)) ? 2 : 0); + screen()->set16bitShadingLevel(4); + gui_drawBox(x, (_dialogueButtonYoffs + _dialogueButtonPosY[i]), _dialogueButtonWidth, guiSettings()->buttons.height, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); + screen()->set16bitShadingLevel(0); + screen()->printText(_dialogueButtonString[i], x + (_dialogueButtonWidth >> 1) - (screen()->getTextWidth(_dialogueButtonString[i])) / 2, + (_dialogueButtonYoffs + _dialogueButtonPosY[i]) + 2 - sjisYOffset, _dialogueHighlightedButton == i ? _dialogueButtonLabelColor1 : _dialogueButtonLabelColor2, 0); + } + } + screen()->setFont(of); + screen()->setCurPage(cp); +} + +uint16 KyraRpgEngine::processDialogue() { + int df = _dialogueHighlightedButton; + int res = 0; + + for (int i = 0; i < _dialogueNumButtons; i++) { + int x = _dialogueButtonPosX[i]; + int y = ((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) & ~7) - 1 : (_dialogueButtonYoffs + _dialogueButtonPosY[i])); + Common::Point p = getMousePos(); + if (posWithinRect(p.x, p.y, x, y, x + _dialogueButtonWidth, y + guiSettings()->buttons.height)) { + _dialogueHighlightedButton = i; + break; + } + } + + if (_dialogueNumButtons == 0) { + int e = checkInput(0, false) & 0xFF; + removeInputTop(); + + if (e) { + gui_notifyButtonListChanged(); + + if (e == _keyMap[Common::KEYCODE_SPACE] || e == _keyMap[Common::KEYCODE_RETURN]) { + snd_stopSpeech(true); + } + } + + if (snd_updateCharacterSpeech() != 2) { + res = 1; + if (!shouldQuit()) { + removeInputTop(); + gui_notifyButtonListChanged(); + } + } + } else { + int e = checkInput(0, false, 0) & 0xFF; + removeInputTop(); + if (e) + gui_notifyButtonListChanged(); + + if ((_flags.gameID == GI_LOL && (e == 200 || e == 202)) || (_flags.gameID != GI_LOL && (e == 199 || e == 201))) { + for (int i = 0; i < _dialogueNumButtons; i++) { + int x = _dialogueButtonPosX[i]; + int y = (gameFlags().use16ColorMode ? ((_dialogueButtonYoffs + _dialogueButtonPosY[i]) & ~7) - 1 : (_dialogueButtonYoffs + _dialogueButtonPosY[i])); + Common::Point p = getMousePos(); + if (posWithinRect(p.x, p.y, x, y, x + _dialogueButtonWidth, y + guiSettings()->buttons.height)) { + _dialogueHighlightedButton = i; + res = _dialogueHighlightedButton + 1; + break; + } + } + } else if (e == _keyMap[Common::KEYCODE_SPACE] || e == _keyMap[Common::KEYCODE_RETURN]) { + snd_stopSpeech(true); + res = _dialogueHighlightedButton + 1; + } else if (e == _keyMap[Common::KEYCODE_LEFT] || e == _keyMap[Common::KEYCODE_DOWN]) { + if (_dialogueNumButtons > 1 && _dialogueHighlightedButton > 0) + _dialogueHighlightedButton--; + } else if (e == _keyMap[Common::KEYCODE_RIGHT] || e == _keyMap[Common::KEYCODE_UP]) { + if (_dialogueNumButtons > 1 && _dialogueHighlightedButton < (_dialogueNumButtons - 1)) + _dialogueHighlightedButton++; + } + } + + if (df != _dialogueHighlightedButton) + drawDialogueButtons(); + + screen()->updateScreen(); + + if (res == 0) + return 0; + + stopPortraitSpeechAnim(); + + if (game() == GI_LOL) { + if (!textEnabled() && _currentControlMode) { + screen()->setScreenDim(5); + const ScreenDim *d = screen()->getScreenDim(5); + screen()->fillRect(d->sx, d->sy + d->h - 9, d->sx + d->w - 1, d->sy + d->h - 1, d->unkA); + } else { + const ScreenDim *d = screen()->_curDim; + if (gameFlags().use16ColorMode) + screen()->fillRect(d->sx, d->sy, d->sx + d->w - 3, d->sy + d->h - 2, d->unkA); + else + screen()->fillRect(d->sx, d->sy, d->sx + d->w - 2, d->sy + d->h - 1, d->unkA); + txt()->clearDim(4); + txt()->resetDimTextPositions(4); + } + } + + return res; +} + +void KyraRpgEngine::delayUntil(uint32 time, bool, bool doUpdate, bool isMainLoop) { + uint32 curTime = _system->getMillis(); + if (time > curTime) + delay(time - curTime, doUpdate, isMainLoop); +} + +int KyraRpgEngine::rollDice(int times, int pips, int inc) { + if (times <= 0 || pips <= 0) + return inc; + + int res = 0; + while (times--) + res += _rnd.getRandomNumberRng(1, pips); + + return res + inc; +} + +bool KyraRpgEngine::snd_processEnvironmentalSoundEffect(int soundId, int block) { + if (!_sound->sfxEnabled() || shouldQuit()) + return false; + + if (_environmentSfx) + snd_playSoundEffect(_environmentSfx, _environmentSfxVol); + + int dist = 0; + if (block) { + dist = getBlockDistance(_currentBlock, block); + if (dist > _envSfxDistThreshold) { + _environmentSfx = 0; + return false; + } + } + + _environmentSfx = soundId; + _environmentSfxVol = (_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? (dist ? (16 - dist) * 8 - 1 : 127) : ((15 - ((block || (_flags.gameID == GI_LOL && dist < 2)) ? dist : 0)) << 4); + + return true; +} + +void KyraRpgEngine::updateEnvironmentalSfx(int soundId) { + snd_processEnvironmentalSoundEffect(soundId, _currentBlock); +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB || ENABLE_LOL diff --git a/engines/kyra/engine/kyra_rpg.h b/engines/kyra/engine/kyra_rpg.h new file mode 100644 index 0000000000..a446c87a0e --- /dev/null +++ b/engines/kyra/engine/kyra_rpg.h @@ -0,0 +1,388 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_RPG_H +#define KYRA_RPG_H + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) + +#include "kyra/kyra_v1.h" +#include "kyra/graphics/screen_eob.h" +#include "kyra/gui/gui_eob.h" +#include "kyra/text/text_lol.h" + +namespace Kyra { + +struct LevelDecorationProperty { + uint16 shapeIndex[10]; + uint8 scaleFlag[10]; + int16 shapeX[10]; + int16 shapeY[10]; + int8 next; + uint8 flags; +}; + +struct LevelBlockProperty { + uint8 walls[4]; + uint16 assignedObjects; + uint16 drawObjects; + uint8 direction; + uint16 flags; +}; + +struct OpenDoorState { + uint16 block; + int8 wall; + int8 state; +}; + +struct LevelTempData { + uint8 *wallsXorData; + uint16 *flags; + void *monsters; + void *flyingObjects; + void *wallsOfForce; + uint8 monsterDifficulty; +}; + +struct EoBFlyingObject { + uint8 enable; + uint8 objectType; + int16 attackerId; + Item item; + uint16 curBlock; + uint16 starting; + uint8 u1; + uint8 direction; + uint8 distance; + int8 callBackIndex; + uint8 curPos; + uint8 flags; + uint8 unused; +}; + +struct KyraRpgGUISettings { + struct DialogueButtons { + uint8 labelColor1; + uint8 labelColor2; + uint16 width; + uint16 height; + int waitReserve; + uint16 waitX[2]; + uint8 waitY[2]; + uint16 waitWidth[2]; + } buttons; + + struct Colors { + uint8 frame1; + uint8 frame2; + int fill; + + uint8 unused; + uint8 barGraph; + + uint8 warningFrame1; + uint8 warningFrame2; + int warningFill; + + uint8 extraFrame1; + uint8 extraFrame2; + int extraFill; + + uint8 inactiveTabFrame1; + uint8 inactiveTabFrame2; + int inactiveTabFill; + } colors; +}; + +class KyraRpgEngine : public KyraEngine_v1 { +friend class TextDisplayer_rpg; +public: + KyraRpgEngine(OSystem *system, const GameFlags &flags); + virtual ~KyraRpgEngine(); + + virtual Screen *screen() = 0; + virtual GUI *gui() const = 0; + +protected: + // Startup + virtual Common::Error init(); + virtual Common::Error go() = 0; + + // Init + void initStaticResource(); + + const uint8 **_itemIconShapes; + + // Main loop + virtual void update() = 0; + void updateEnvironmentalSfx(int soundId); + + // timers + virtual void setupTimers() = 0; + void enableSysTimer(int sysTimer); + void disableSysTimer(int sysTimer); + void enableTimer(int id); + virtual uint8 getClock2Timer(int index) = 0; + virtual uint8 getNumClock2Timers() = 0; + + void timerProcessDoors(int timerNum); + + // mouse + bool posWithinRect(int posX, int posY, int x1, int y1, int x2, int y2); + virtual void setHandItem(Item itemIndex) = 0; + + // Characters + int _updateCharNum; + int _updatePortraitSpeechAnimDuration; + bool _fadeText; + int _resetPortraitAfterSpeechAnim; + int _needSceneRestore; + + // Items + int _itemInHand; + + // Monsters + int getBlockDistance(uint16 block1, uint16 block2); + + uint8 **_monsterPalettes; + uint8 **_monsterShapes; + + int16 _shpDmX1; + int16 _shpDmX2; + + int _monsterStepCounter; + int _monsterStepMode; + + // Level + virtual void addLevelItems() = 0; + virtual void loadBlockProperties(const char *file) = 0; + + virtual void drawScene(int pageNum) = 0; + virtual void drawSceneShapes(int start) = 0; + virtual void drawDecorations(int index) = 0; + + virtual const uint8 *getBlockFileData(int levelIndex) = 0; + void setLevelShapesDim(int index, int16 &x1, int16 &x2, int dim); + void setDoorShapeDim(int index, int16 &y1, int16 &y2, int dim); + void drawLevelModifyScreenDim(int dim, int16 x1, int16 y1, int16 x2, int16 y2); + void generateBlockDrawingBuffer(); + void generateVmpTileData(int16 startBlockX, uint8 startBlockY, uint8 wllVmpIndex, int16 vmpOffset, uint8 numBlocksX, uint8 numBlocksY); + void generateVmpTileDataFlipped(int16 startBlockX, uint8 startBlockY, uint8 wllVmpIndex, int16 vmpOffset, uint8 numBlocksX, uint8 numBlocksY); + bool hasWall(int index); + void assignVisibleBlocks(int block, int direction); + bool checkSceneUpdateNeed(int block); + void drawVcnBlocks(); + uint16 calcNewBlockPosition(uint16 curBlock, uint16 direction); + + virtual int clickedDoorSwitch(uint16 block, uint16 direction) = 0; + int clickedWallShape(uint16 block, uint16 direction); + int clickedLeverOn(uint16 block, uint16 direction); + int clickedLeverOff(uint16 block, uint16 direction); + int clickedWallOnlyScript(uint16 block); + virtual int clickedNiche(uint16 block, uint16 direction) = 0; + + void processDoorSwitch(uint16 block, int openClose); + void openCloseDoor(int block, int openClose); + void completeDoorOperations(); + + uint8 *_wllVmpMap; + int8 *_wllShapeMap; + uint8 *_specialWallTypes; + uint8 *_wllWallFlags; + + int _sceneXoffset; + int _sceneShpDim; + + LevelBlockProperty *_levelBlockProperties; + LevelBlockProperty *_visibleBlocks[18]; + LevelDecorationProperty *_levelDecorationData; + uint16 _levelDecorationDataSize; + LevelDecorationProperty *_levelDecorationProperties; + uint8 **_levelDecorationShapes; + uint16 _decorationCount; + int16 _mappedDecorationsCount; + uint16 *_vmpPtr; + uint8 *_vcnBlocks; + uint8 *_vcfBlocks; + uint8 *_vcnTransitionMask; + uint8 *_vcnShift; + uint8 *_vcnColTable; + uint8 _vcnBpp; + uint16 *_blockDrawingBuffer; + uint8 *_sceneWindowBuffer; + uint8 _blockBrightness; + uint8 _wllVcnOffset; + + uint8 **_doorShapes; + + uint8 _currentLevel; + uint16 _currentBlock; + uint16 _currentDirection; + int _sceneDefaultUpdate; + bool _sceneUpdateRequired; + + int16 _visibleBlockIndex[18]; + int16 *_lvlShapeLeftRight; + int16 *_lvlShapeTop; + int16 *_lvlShapeBottom; + + char _lastBlockDataFile[13]; + uint32 _hasTempDataFlags; + + int16 _sceneDrawVarDown; + int16 _sceneDrawVarRight; + int16 _sceneDrawVarLeft; + int _wllProcessFlag; + + OpenDoorState _openDoorState[3]; + + int _sceneDrawPage1; + int _sceneDrawPage2; + + const int8 *_dscShapeIndex; + const uint8 *_dscDimMap; + const int8 *_dscDim1; + const int8 *_dscDim2; + const int16 *_dscShapeX; + const uint8 *_dscDoorScaleOffs; + const uint8 *_dscBlockMap; + const int8 *_dscBlockIndex; + const uint8 *_dscTileIndex; + + const uint8 *_dscDoorShpIndex; + int _dscDoorShpIndexSize; + const uint8 *_dscDoorY2; + const uint8 *_dscDoorFrameY1; + const uint8 *_dscDoorFrameY2; + const uint8 *_dscDoorFrameIndex1; + const uint8 *_dscDoorFrameIndex2; + + // Script + virtual void runLevelScript(int block, int flags) = 0; + + // Gui + void removeInputTop(); + void gui_drawBox(int x, int y, int w, int h, int frameColor1, int frameColor2, int fillColor); + virtual void gui_drawHorizontalBarGraph(int x, int y, int w, int h, int32 curVal, int32 maxVal, int col1, int col2); + void gui_initButtonsFromList(const uint8 *list); + virtual void gui_initButton(int index, int x = -1, int y = -1, int val = -1) = 0; + void gui_resetButtonList(); + void gui_notifyButtonListChanged(); + + bool clickedShape(int shapeIndex); + + virtual const KyraRpgGUISettings *guiSettings() = 0; + + int _clickedShapeXOffs; + int _clickedShapeYOffs; + + Button *_activeButtons; + Button _activeButtonData[70]; + Common::Array _buttonCallbacks; + //bool _processingButtons; + + uint8 _mouseClick; + bool _preserveEvents; + bool _buttonListChanged; + + int _updateFlags; + int _clickedSpecialFlag; + + int _compassDirection; + + static const uint8 _dropItemDirIndex[]; + + // text + void drawDialogueButtons(); + uint16 processDialogue(); + + TextDisplayer_rpg *_txt; + virtual TextDisplayer_rpg *txt() { return _txt; } + + bool _dialogueField; + + const char *_dialogueButtonString[9]; + const uint16 *_dialogueButtonPosX; + const uint8 *_dialogueButtonPosY; + int16 _dialogueButtonYoffs; + uint16 _dialogueButtonWidth; + int _dialogueNumButtons; + int _dialogueHighlightedButton; + int _currentControlMode; + int _specialSceneFlag; + uint8 _dialogueButtonLabelColor1; + uint8 _dialogueButtonLabelColor2; + + const char *const *_moreStrings; + + // misc + virtual void delay(uint32 millis, bool doUpdate = false, bool isMainLoop = false) = 0; + void delayUntil(uint32 time, bool unused = false, bool doUpdate = false, bool isMainLoop = false); + int rollDice(int times, int pips, int inc = 0); + + virtual Common::Error loadGameState(int slot) = 0; + virtual Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail) = 0; + + void generateTempData(); + virtual void restoreBlockTempData(int levelIndex); + void releaseTempData(); + virtual void *generateMonsterTempData(LevelTempData *tmp) = 0; + virtual void restoreMonsterTempData(LevelTempData *tmp) = 0; + virtual void releaseMonsterTempData(LevelTempData *tmp) = 0; + void restoreFlyingObjectTempData(LevelTempData *tmp); + void *generateFlyingObjectTempData(LevelTempData *tmp); + void releaseFlyingObjectTempData(LevelTempData *tmp); + virtual void *generateWallOfForceTempData(LevelTempData *tmp) { return 0; } + virtual void restoreWallOfForceTempData(LevelTempData *tmp) {} + virtual void releaseWallOfForceTempData(LevelTempData *tmp) {} + + LevelTempData *_lvlTempData[29]; + const int _numFlyingObjects; + uint32 _flyingObjectStructSize; + void *_flyingObjectsPtr; + + // sound + virtual bool snd_processEnvironmentalSoundEffect(int soundId, int block); + virtual void snd_stopSpeech(bool) {} + virtual int snd_updateCharacterSpeech() { return 0; } + virtual void stopPortraitSpeechAnim() {} + virtual void setupOpcodeTable() {} + virtual void snd_playVoiceFile(int) {} + + int _environmentSfx; + int _environmentSfxVol; + int _envSfxDistThreshold; + + uint32 _activeVoiceFileTotalTime; + + // unused + void setWalkspeed(uint8) {} + void removeHandItem() {} + bool lineIsPassable(int, int) { return false; } +}; + +} // End of namespace Kyra + +#endif // ENABLE_EOB || ENABLE_LOL + +#endif diff --git a/engines/kyra/engine/kyra_v1.cpp b/engines/kyra/engine/kyra_v1.cpp new file mode 100644 index 0000000000..e2896eb1a5 --- /dev/null +++ b/engines/kyra/engine/kyra_v1.cpp @@ -0,0 +1,698 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/kyra_v1.h" +#include "kyra/sound/sound_intern.h" +#include "kyra/resource/resource.h" +#include "kyra/engine/timer.h" +#include "kyra/gui/debugger.h" + +#include "common/error.h" +#include "common/config-manager.h" +#include "common/debug-channels.h" + +namespace Kyra { + +KyraEngine_v1::KyraEngine_v1(OSystem *system, const GameFlags &flags) + : Engine(system), _flags(flags), _rnd("kyra") { + _res = 0; + _sound = 0; + _text = 0; + _staticres = 0; + _timer = 0; + _emc = 0; + _debugger = 0; + + _configRenderMode = Common::kRenderDefault; + + if (_flags.platform == Common::kPlatformAmiga) + _gameSpeed = 50; + else + _gameSpeed = 60; + _tickLength = (uint8)(1000.0 / _gameSpeed); + + _trackMap = 0; + _trackMapSize = 0; + _lastMusicCommand = -1; + _curSfxFile = _curMusicTheme = -1; + + _gameToLoad = -1; + + _mouseState = -1; + _deathHandler = -1; + + memset(_flagsTable, 0, sizeof(_flagsTable)); + + _isSaveAllowed = false; + + _mouseX = _mouseY = 0; + + // sets up all engine specific debug levels + DebugMan.addDebugChannel(kDebugLevelScriptFuncs, "ScriptFuncs", "Script function debug level"); + DebugMan.addDebugChannel(kDebugLevelScript, "Script", "Script interpreter debug level"); + DebugMan.addDebugChannel(kDebugLevelSprites, "Sprites", "Sprite debug level"); + DebugMan.addDebugChannel(kDebugLevelScreen, "Screen", "Screen debug level"); + DebugMan.addDebugChannel(kDebugLevelSound, "Sound", "Sound debug level"); + DebugMan.addDebugChannel(kDebugLevelAnimator, "Animator", "Animator debug level"); + DebugMan.addDebugChannel(kDebugLevelMain, "Main", "Generic debug level"); + DebugMan.addDebugChannel(kDebugLevelGUI, "GUI", "GUI debug level"); + DebugMan.addDebugChannel(kDebugLevelSequence, "Sequence", "Sequence debug level"); + DebugMan.addDebugChannel(kDebugLevelMovie, "Movie", "Movie debug level"); + DebugMan.addDebugChannel(kDebugLevelTimer, "Timer", "Timer debug level"); +} + +::GUI::Debugger *KyraEngine_v1::getDebugger() { + return _debugger; +} + +void KyraEngine_v1::pauseEngineIntern(bool pause) { + Engine::pauseEngineIntern(pause); + if (_sound) + _sound->pause(pause); + if (_timer) + _timer->pause(pause); +} + +Common::Error KyraEngine_v1::init() { + // Setup mixer + syncSoundSettings(); + + if (!_flags.useDigSound) { + if (_flags.platform == Common::kPlatformFMTowns) { + if (_flags.gameID == GI_KYRA1) + _sound = new SoundTowns(this, _mixer); + else + _sound = new SoundTownsPC98_v2(this, _mixer); + } else if (_flags.platform == Common::kPlatformPC98) { + if (_flags.gameID == GI_KYRA1) + _sound = new SoundPC98(this, _mixer); + else + _sound = new SoundTownsPC98_v2(this, _mixer); + } else if (_flags.platform == Common::kPlatformAmiga) { + _sound = new SoundAmiga(this, _mixer); + } else { + // In Kyra 1 users who have specified a default MT-32 device in the launcher settings + // will get MT-32 music, otherwise AdLib. In Kyra 2 and LoL users who have specified a + // default GM device in the launcher will get GM music, otherwise AdLib. Users who want + // MT-32 music in Kyra2 or LoL have to select this individually (since we assume that + // most users rather have a GM device than a MT-32 device). + // Users who want PC speaker sound always have to select this individually for all + // Kyra games. + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_PCSPK | MDT_MIDI | MDT_ADLIB | ((_flags.gameID == GI_KYRA2 || _flags.gameID == GI_LOL) ? MDT_PREFER_GM : MDT_PREFER_MT32)); + if (MidiDriver::getMusicType(dev) == MT_ADLIB) { + _sound = new SoundAdLibPC(this, _mixer); + } else { + Sound::kType type; + const MusicType midiType = MidiDriver::getMusicType(dev); + + if (midiType == MT_PCSPK || midiType == MT_NULL) + type = Sound::kPCSpkr; + else if (midiType == MT_MT32 || ConfMan.getBool("native_mt32")) + type = Sound::kMidiMT32; + else + type = Sound::kMidiGM; + + MidiDriver *driver = 0; + + if (MidiDriver::getMusicType(dev) == MT_PCSPK) { + driver = new MidiDriver_PCSpeaker(_mixer); + } else { + driver = MidiDriver::createMidi(dev); + if (type == Sound::kMidiMT32) + driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + } + + assert(driver); + + SoundMidiPC *soundMidiPc = new SoundMidiPC(this, _mixer, driver, type); + _sound = soundMidiPc; + assert(_sound); + + // Unlike some SCUMM games, it's not that the MIDI sounds are + // missing. It's just that at least at the time of writing they + // are decidedly inferior to the AdLib ones. + if (ConfMan.getBool("multi_midi")) { + SoundAdLibPC *adlib = new SoundAdLibPC(this, _mixer); + assert(adlib); + + _sound = new MixedSoundDriver(this, _mixer, soundMidiPc, adlib); + } + } + } + + assert(_sound); + } + + if (_sound) + _sound->updateVolumeSettings(); + + if (ConfMan.hasKey("render_mode")) + _configRenderMode = Common::parseRenderMode(ConfMan.get("render_mode")); + + _res = new Resource(this); + assert(_res); + _res->reset(); + + _staticres = new StaticResource(this); + assert(_staticres); + if (!_staticres->init()) + error("_staticres->init() failed"); + assert(screen()); + if (!screen()->init()) + error("screen()->init() failed"); + _timer = new TimerManager(this, _system); + assert(_timer); + setupTimers(); + _emc = new EMCInterpreter(this); + assert(_emc); + + setupOpcodeTable(); + readSettings(); + + if (ConfMan.hasKey("save_slot")) { + _gameToLoad = ConfMan.getInt("save_slot"); + if (!saveFileLoadable(_gameToLoad)) + _gameToLoad = -1; + } + + setupKeyMap(); + + // Prevent autosave on game startup + _lastAutosave = _system->getMillis(); + + return Common::kNoError; +} + +KyraEngine_v1::~KyraEngine_v1() { + for (Common::Array::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i) + delete *i; + _opcodes.clear(); + _keyMap.clear(); + + delete _res; + delete _staticres; + delete _sound; + delete _text; + delete _timer; + delete _emc; + delete _debugger; +} + +Common::Point KyraEngine_v1::getMousePos() { + Common::Point mouse = _eventMan->getMousePos(); + + if (_flags.useHiRes) { + mouse.x >>= 1; + mouse.y >>= 1; + } + + return mouse; +} + +void KyraEngine_v1::setMousePos(int x, int y) { + if (_flags.useHiRes) { + x <<= 1; + y <<= 1; + } + _system->warpMouse(x, y); +} + +int KyraEngine_v1::checkInput(Button *buttonList, bool mainLoop, int eventFlag) { + _isSaveAllowed = mainLoop; + updateInput(); + _isSaveAllowed = false; + + if (mainLoop) + checkAutosave(); + + int keys = 0; + int8 mouseWheel = 0; + + while (!_eventList.empty()) { + Common::Event event = *_eventList.begin(); + bool breakLoop = false; + + switch (event.type) { + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode >= Common::KEYCODE_1 && event.kbd.keycode <= Common::KEYCODE_9 && + (event.kbd.hasFlags(Common::KBD_CTRL) || event.kbd.hasFlags(Common::KBD_ALT)) && mainLoop) { + int saveLoadSlot = 9 - (event.kbd.keycode - Common::KEYCODE_0) + 990; + + if (event.kbd.hasFlags(Common::KBD_CTRL)) { + if (saveFileLoadable(saveLoadSlot)) + loadGameStateCheck(saveLoadSlot); + _eventList.clear(); + breakLoop = true; + } else { + char savegameName[14]; + sprintf(savegameName, "Quicksave %d", event.kbd.keycode - Common::KEYCODE_0); + saveGameStateIntern(saveLoadSlot, savegameName, 0); + } + } else if (event.kbd.hasFlags(Common::KBD_CTRL)) { + if (event.kbd.keycode == Common::KEYCODE_d) { + if (_debugger) + _debugger->attach(); + breakLoop = true; + } else if (event.kbd.keycode == Common::KEYCODE_q) { + quitGame(); + } + } else { + KeyMap::const_iterator keycode = _keyMap.find(event.kbd.keycode); + if (keycode != _keyMap.end()) { + keys = keycode->_value; + if (event.kbd.flags & Common::KBD_SHIFT) + keys |= 0x100; + } else { + keys = 0; + } + + // When we got an keypress, which we might need to handle, + // break the event loop and pass it to GUI code. + if (keys) + breakLoop = true; + } + break; + + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_LBUTTONUP: { + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + if (_flags.useHiRes) { + _mouseX >>= 1; + _mouseY >>= 1; + } + keys = (event.type == Common::EVENT_LBUTTONDOWN ? 199 : (200 | 0x800)); + breakLoop = true; + } break; + + case Common::EVENT_RBUTTONDOWN: + case Common::EVENT_RBUTTONUP: { + _mouseX = event.mouse.x; + _mouseY = event.mouse.y; + if (_flags.useHiRes) { + _mouseX >>= 1; + _mouseY >>= 1; + } + keys = (event.type == Common::EVENT_RBUTTONDOWN ? 201 : (202 | 0x800)); + breakLoop = true; + } break; + + case Common::EVENT_WHEELUP: + mouseWheel = -1; + break; + + case Common::EVENT_WHEELDOWN: + mouseWheel = 1; + break; + + default: + break; + } + + if (_debugger) + _debugger->onFrame(); + + if (breakLoop) + break; + + _eventList.erase(_eventList.begin()); + } + + GUI *guiInstance = gui(); + if (guiInstance) { + if (keys) + return guiInstance->processButtonList(buttonList, keys | eventFlag, mouseWheel); + else + return guiInstance->processButtonList(buttonList, 0, mouseWheel); + } else { + return keys; + } +} + +void KyraEngine_v1::setupKeyMap() { + struct KeyCodeMapEntry { + Common::KeyCode kcScummVM; + int16 kcDOS; + int16 kcPC98; + int16 kcFMTowns; + }; + +#define UNKNOWN_KEYCODE -1 +#define KC(x) Common::KEYCODE_##x + static const KeyCodeMapEntry keys[] = { + { KC(SPACE), 61, 53, 32 }, + { KC(RETURN), 43, 29, 13 }, + { KC(UP), 96, 68, 30 }, + { KC(KP8), 96, 68, 30 }, + { KC(RIGHT), 102, 73, 28 }, + { KC(KP6), 102, 73, 28 }, + { KC(DOWN), 98, 76, 31 }, + { KC(KP2), 98, 76, 31 }, + { KC(KP5), 97, 72, UNKNOWN_KEYCODE }, + { KC(LEFT), 92, 71, 29 }, + { KC(KP4), 92, 71, 29 }, + { KC(HOME), 91, 67, 127 }, + { KC(KP7), 91, 67, 127 }, + { KC(PAGEUP), 101, 69, 18 }, + { KC(KP9), 101, 69, 18 }, + { KC(END), 93, UNKNOWN_KEYCODE, UNKNOWN_KEYCODE }, + { KC(KP1), 93, UNKNOWN_KEYCODE, UNKNOWN_KEYCODE }, + { KC(PAGEDOWN), 103, UNKNOWN_KEYCODE, UNKNOWN_KEYCODE }, + { KC(KP3), 103, UNKNOWN_KEYCODE, UNKNOWN_KEYCODE }, + { KC(F1), 112, 99, 93 }, + { KC(F2), 113, 100, 94 }, + { KC(F3), 114, 101, 95 }, + { KC(F4), 115, 102, 96 }, + { KC(F5), 116, 103, 97 }, + { KC(F6), 117, 104, 98 }, + { KC(a), 31, 31, UNKNOWN_KEYCODE }, + { KC(b), 50, 50, 66 }, + { KC(c), 48, 48, 67 }, + { KC(d), 33, 33, 68 }, + { KC(e), 19, 19, UNKNOWN_KEYCODE }, + { KC(f), 34, 34, 70 }, + { KC(i), 24, 24, 24 }, + { KC(k), 38, 38, 75 }, + { KC(m), 52, 52, 77 }, + { KC(n), 51, 51, UNKNOWN_KEYCODE }, + { KC(o), 25, 25, 79 }, + { KC(p), 26, 26, 80 }, + { KC(r), 20, 20, 82 }, + { KC(s), 32, 32, UNKNOWN_KEYCODE }, + { KC(w), 18, 18, UNKNOWN_KEYCODE }, + { KC(y), 22, 22, UNKNOWN_KEYCODE }, + { KC(z), 46, 46, UNKNOWN_KEYCODE }, + { KC(0), UNKNOWN_KEYCODE, UNKNOWN_KEYCODE, 48 }, + { KC(1), 2, UNKNOWN_KEYCODE, 49 }, + { KC(2), 3, UNKNOWN_KEYCODE, 50 }, + { KC(3), 4, UNKNOWN_KEYCODE, 51 }, + { KC(4), 5, UNKNOWN_KEYCODE, 52 }, + { KC(5), 6, UNKNOWN_KEYCODE, 53 }, + { KC(6), 7, UNKNOWN_KEYCODE, 54 }, + { KC(7), 8, UNKNOWN_KEYCODE, 55 }, + { KC(SLASH), 55, 55, 47 }, + { KC(ESCAPE), 110, 1, 27 }, + { KC(MINUS), 12, UNKNOWN_KEYCODE, 45 }, + { KC(KP_MINUS), 105, UNKNOWN_KEYCODE, 45 }, + { KC(PLUS), 13, UNKNOWN_KEYCODE, 43 }, + { KC(KP_PLUS), 106, UNKNOWN_KEYCODE, 43 }, + + // Multiple mappings for the keys to the right of the 'M' key, + // since these are different for QWERTZ, QWERTY and AZERTY keyboards. + // QWERTZ + { KC(COMMA), 53, UNKNOWN_KEYCODE, 60 }, + { KC(PERIOD), 54, UNKNOWN_KEYCODE, 62 }, + // AZERTY + { KC(SEMICOLON), 53, UNKNOWN_KEYCODE, 60 }, + { KC(COLON), 54, UNKNOWN_KEYCODE, 62 }, + // QWERTY + { KC(LESS), 53, UNKNOWN_KEYCODE, 60 }, + { KC(GREATER), 54, UNKNOWN_KEYCODE, 62 } + }; +#undef KC +#undef UNKNOWN_KEYCODE + + _keyMap.clear(); + + for (int i = 0; i < ARRAYSIZE(keys); i++) + _keyMap[keys[i].kcScummVM] = (_flags.platform == Common::kPlatformPC98) ? keys[i].kcPC98 : ((_flags.platform == Common::kPlatformFMTowns) ? keys[i].kcFMTowns : keys[i].kcDOS); +} + +void KyraEngine_v1::updateInput() { + Common::Event event; + + bool updateScreen = false; + + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode == Common::KEYCODE_PERIOD || event.kbd.keycode == Common::KEYCODE_ESCAPE || + event.kbd.keycode == Common::KEYCODE_SPACE || event.kbd.keycode == Common::KEYCODE_RETURN || + event.kbd.keycode == Common::KEYCODE_UP || event.kbd.keycode == Common::KEYCODE_RIGHT || + event.kbd.keycode == Common::KEYCODE_DOWN || event.kbd.keycode == Common::KEYCODE_LEFT) + _eventList.push_back(Event(event, true)); + else if (event.kbd.keycode == Common::KEYCODE_q && event.kbd.hasFlags(Common::KBD_CTRL)) + quitGame(); + else + _eventList.push_back(event); + break; + + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_RBUTTONDOWN: + _eventList.push_back(Event(event, true)); + break; + + case Common::EVENT_MOUSEMOVE: + if (screen()->isMouseVisible()) + updateScreen = true; + break; + + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONUP: + case Common::EVENT_WHEELUP: + case Common::EVENT_WHEELDOWN: + _eventList.push_back(event); + break; + + default: + break; + } + } + + if (updateScreen) + _system->updateScreen(); +} + +void KyraEngine_v1::removeInputTop() { + if (!_eventList.empty()) + _eventList.erase(_eventList.begin()); +} + +bool KyraEngine_v1::skipFlag() const { + for (Common::List::const_iterator i = _eventList.begin(); i != _eventList.end(); ++i) { + if (i->causedSkip) + return true; + } + return false; +} + +void KyraEngine_v1::resetSkipFlag(bool removeEvent) { + for (Common::List::iterator i = _eventList.begin(); i != _eventList.end(); ++i) { + if (i->causedSkip) { + if (removeEvent) + _eventList.erase(i); + else + i->causedSkip = false; + return; + } + } +} + + +int KyraEngine_v1::setGameFlag(int flag) { + assert((flag >> 3) >= 0 && (flag >> 3) <= ARRAYSIZE(_flagsTable)); + _flagsTable[flag >> 3] |= (1 << (flag & 7)); + return 1; +} + +int KyraEngine_v1::queryGameFlag(int flag) const { + assert((flag >> 3) >= 0 && (flag >> 3) <= ARRAYSIZE(_flagsTable)); + return ((_flagsTable[flag >> 3] >> (flag & 7)) & 1); +} + +int KyraEngine_v1::resetGameFlag(int flag) { + assert((flag >> 3) >= 0 && (flag >> 3) <= ARRAYSIZE(_flagsTable)); + _flagsTable[flag >> 3] &= ~(1 << (flag & 7)); + return 0; +} + +void KyraEngine_v1::delayUntil(uint32 timestamp, bool updateTimers, bool update, bool isMainLoop) { + const uint32 curTime = _system->getMillis(); + if (curTime > timestamp) + return; + + uint32 del = timestamp - curTime; + while (del && !shouldQuit()) { + uint32 step = MIN(del, _tickLength); + delay(step, update, isMainLoop); + del -= step; + } +} + +void KyraEngine_v1::delay(uint32 amount, bool update, bool isMainLoop) { + _system->delayMillis(amount); +} + +void KyraEngine_v1::delayWithTicks(int ticks) { + delay(ticks * _tickLength); +} + +void KyraEngine_v1::registerDefaultSettings() { + if (_flags.platform == Common::kPlatformFMTowns) + ConfMan.registerDefault("cdaudio", true); + if (_flags.fanLang != Common::UNK_LANG) { + // HACK/WORKAROUND: Since we can't use registerDefault here to overwrite + // the global subtitles settings, we're using this hack to enable subtitles + // for fan translations + const Common::ConfigManager::Domain *cur = ConfMan.getActiveDomain(); + if (!cur || (cur && cur->getVal("subtitles").empty())) + ConfMan.setBool("subtitles", true); + } +} + +void KyraEngine_v1::readSettings() { + _configWalkspeed = ConfMan.getInt("walkspeed"); + _configMusic = 0; + + if (!ConfMan.getBool("music_mute")) { + if (_flags.platform == Common::kPlatformFMTowns) + _configMusic = ConfMan.getBool("cdaudio") ? 2 : 1; + else + _configMusic = 1; + } + _configSounds = ConfMan.getBool("sfx_mute") ? 0 : 1; + + if (_sound) { + _sound->enableMusic(_configMusic); + _sound->enableSFX(_configSounds); + } + + bool speechMute = ConfMan.getBool("speech_mute"); + bool subtitles = ConfMan.getBool("subtitles"); + + if (!speechMute && subtitles) + _configVoice = 2; // Voice & Text + else if (!speechMute && !subtitles) + _configVoice = 1; // Voice only + else + _configVoice = 0; // Text only + + setWalkspeed(_configWalkspeed); +} + +void KyraEngine_v1::writeSettings() { + bool speechMute, subtitles; + + ConfMan.setInt("walkspeed", _configWalkspeed); + ConfMan.setBool("music_mute", _configMusic == 0); + if (_flags.platform == Common::kPlatformFMTowns) + ConfMan.setBool("cdaudio", _configMusic == 2); + ConfMan.setBool("sfx_mute", _configSounds == 0); + + switch (_configVoice) { + case 0: // Text only + speechMute = true; + subtitles = true; + break; + case 1: // Voice only + speechMute = false; + subtitles = false; + break; + default: // Voice & Text + speechMute = false; + subtitles = true; + } + + if (_sound) { + if (!_configMusic) + _sound->beginFadeOut(); + _sound->enableMusic(_configMusic); + _sound->enableSFX(_configSounds); + } + + ConfMan.setBool("speech_mute", speechMute); + ConfMan.setBool("subtitles", subtitles); + + ConfMan.flushToDisk(); +} + +bool KyraEngine_v1::speechEnabled() { + return _flags.isTalkie && (_configVoice == 1 || _configVoice == 2); +} + +bool KyraEngine_v1::textEnabled() { + return !_flags.isTalkie || (_configVoice == 0 || _configVoice == 2); +} + +int KyraEngine_v1::convertVolumeToMixer(int value) { + value -= 2; + return (value * Audio::Mixer::kMaxMixerVolume) / 95; +} + +int KyraEngine_v1::convertVolumeFromMixer(int value) { + return (value * 95) / Audio::Mixer::kMaxMixerVolume + 2; +} + +void KyraEngine_v1::setVolume(kVolumeEntry vol, uint8 value) { + switch (vol) { + case kVolumeMusic: + ConfMan.setInt("music_volume", convertVolumeToMixer(value)); + break; + + case kVolumeSfx: + ConfMan.setInt("sfx_volume", convertVolumeToMixer(value)); + break; + + case kVolumeSpeech: + ConfMan.setInt("speech_volume", convertVolumeToMixer(value)); + break; + } + + // Resetup mixer + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume")); + _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume")); + if (_sound) + _sound->updateVolumeSettings(); +} + +uint8 KyraEngine_v1::getVolume(kVolumeEntry vol) { + switch (vol) { + case kVolumeMusic: + return convertVolumeFromMixer(ConfMan.getInt("music_volume")); + + case kVolumeSfx: + return convertVolumeFromMixer(ConfMan.getInt("sfx_volume")); + + case kVolumeSpeech: + if (speechEnabled()) + return convertVolumeFromMixer(ConfMan.getInt("speech_volume")); + else + return 2; + break; + } + + return 2; +} + +void KyraEngine_v1::syncSoundSettings() { + Engine::syncSoundSettings(); + + // We need to use this here to allow the subtitle options to be changed + // through the GMM's options dialog. + readSettings(); + + if (_sound) + _sound->updateVolumeSettings(); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/kyra_v2.cpp b/engines/kyra/engine/kyra_v2.cpp new file mode 100644 index 0000000000..e606a66c15 --- /dev/null +++ b/engines/kyra/engine/kyra_v2.cpp @@ -0,0 +1,244 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_v2.h" +#include "kyra/graphics/screen_v2.h" + +#include "common/config-manager.h" +#include "common/error.h" +#include "common/system.h" + +namespace Kyra { + +KyraEngine_v2::KyraEngine_v2(OSystem *system, const GameFlags &flags, const EngineDesc &desc) : KyraEngine_v1(system, flags), _desc(desc) { + memset(&_sceneAnims, 0, sizeof(_sceneAnims)); + memset(&_sceneAnimMovie, 0, sizeof(_sceneAnimMovie)); + + _lastProcessedSceneScript = 0; + _specialSceneScriptRunFlag = false; + + _itemList = 0; + _itemListSize = 0; + + _characterShapeFile = -1; + + _updateCharPosNextUpdate = 0; + + memset(&_sceneScriptState, 0, sizeof(_sceneScriptState)); + memset(&_sceneScriptData, 0, sizeof(_sceneScriptData)); + + Common::fill(_sceneSpecialScriptsTimer, ARRAYEND(_sceneSpecialScriptsTimer), 0); + + _animObjects = 0; + + _runFlag = true; + _showOutro = false; + _deathHandler = -1; + _animNeedUpdate = false; + + _animShapeCount = 0; + _animShapeFiledata = 0; + + _vocHigh = -1; + _chatVocHigh = -1; + _chatVocLow = -1; + _chatText = 0; + _chatObject = -1; + _chatTextEnabled = false; + + memset(_hiddenItems, -1, sizeof(_hiddenItems)); + + _screenBuffer = 0; + + memset(&_mainCharacter, 0, sizeof(_mainCharacter)); + memset(&_mainCharacter.inventory, -1, sizeof(_mainCharacter.inventory)); + + _pauseStart = 0; + + _pathfinderFlag = 0; + _smoothingPath = false; + + _lang = 0; + Common::Language lang = Common::parseLanguage(ConfMan.get("language")); + if (lang == _flags.fanLang && _flags.replacedLang != Common::UNK_LANG) + lang = _flags.replacedLang; + + switch (lang) { + case Common::EN_ANY: + case Common::EN_USA: + case Common::EN_GRB: + _lang = 0; + break; + + case Common::FR_FRA: + _lang = 1; + break; + + case Common::DE_DEU: + _lang = 2; + break; + + case Common::JA_JPN: + _lang = 3; + break; + + default: + warning("unsupported language, switching back to English"); + _lang = 0; + } +} + +KyraEngine_v2::~KyraEngine_v2() { + if (!(_flags.isDemo && !_flags.isTalkie)) { + for (ShapeMap::iterator i = _gameShapes.begin(); i != _gameShapes.end(); ++i) { + delete[] i->_value; + i->_value = 0; + } + _gameShapes.clear(); + } + + delete[] _itemList; + delete[] _sceneList; + + _emc->unload(&_sceneScriptData); + + delete[] _animObjects; + + for (Common::Array::iterator i = _opcodesAnimation.begin(); i != _opcodesAnimation.end(); ++i) + delete *i; + _opcodesAnimation.clear(); + + delete[] _screenBuffer; +} + +void KyraEngine_v2::pauseEngineIntern(bool pause) { + KyraEngine_v1::pauseEngineIntern(pause); + + if (!pause) { + uint32 pausedTime = _system->getMillis() - _pauseStart; + + for (int i = 0; i < ARRAYSIZE(_sceneSpecialScriptsTimer); ++i) { + if (_sceneSpecialScriptsTimer[i]) + _sceneSpecialScriptsTimer[i] += pausedTime; + } + + } else { + _pauseStart = _system->getMillis(); + } +} + +void KyraEngine_v2::delay(uint32 amount, bool updateGame, bool isMainLoop) { + uint32 start = _system->getMillis(); + do { + if (updateGame) { + if (_chatText) + updateWithText(); + else + update(); + } else { + updateInput(); + } + + if (amount > 0) + _system->delayMillis(amount > 10 ? 10 : amount); + } while (!skipFlag() && _system->getMillis() < start + amount && !shouldQuit()); +} + +bool KyraEngine_v2::checkSpecialSceneExit(int num, int x, int y) { + if (_specialExitTable[0 + num] > x || _specialExitTable[5 + num] > y || + _specialExitTable[10 + num] < x || _specialExitTable[15 + num] < y) + return false; + return true; +} + +void KyraEngine_v2::addShapeToPool(const uint8 *data, int realIndex, int shape) { + remShapeFromPool(realIndex); + _gameShapes[realIndex] = screen_v2()->makeShapeCopy(data, shape); +} + +void KyraEngine_v2::addShapeToPool(uint8 *shpData, int index) { + remShapeFromPool(index); + _gameShapes[index] = shpData; +} + +void KyraEngine_v2::remShapeFromPool(int idx) { + ShapeMap::iterator iter = _gameShapes.find(idx); + if (iter != _gameShapes.end()) { + delete[] iter->_value; + iter->_value = 0; + } +} + +uint8 *KyraEngine_v2::getShapePtr(int shape) const { + ShapeMap::iterator iter = _gameShapes.find(shape); + if (iter == _gameShapes.end()) + return 0; + return iter->_value; +} + +void KyraEngine_v2::moveCharacter(int facing, int x, int y) { + x &= ~3; + y &= ~1; + _mainCharacter.facing = facing; + + switch (facing) { + case 0: + while (_mainCharacter.y1 > y) + updateCharPosWithUpdate(); + break; + + case 2: + while (_mainCharacter.x1 < x) + updateCharPosWithUpdate(); + break; + + case 4: + while (_mainCharacter.y1 < y) + updateCharPosWithUpdate(); + break; + + case 6: + while (_mainCharacter.x1 > x) + updateCharPosWithUpdate(); + break; + + default: + break; + } +} + +void KyraEngine_v2::updateCharPosWithUpdate() { + updateCharPos(0, 0); + update(); +} + +int KyraEngine_v2::updateCharPos(int *table, int force) { + if (_updateCharPosNextUpdate > _system->getMillis() && !force) + return 0; + _mainCharacter.x1 += _charAddXPosTable[_mainCharacter.facing]; + _mainCharacter.y1 += _charAddYPosTable[_mainCharacter.facing]; + updateCharAnimFrame(table); + _updateCharPosNextUpdate = _system->getMillis() + getCharacterWalkspeed() * _tickLength; + return 1; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/kyra_v2.h b/engines/kyra/engine/kyra_v2.h new file mode 100644 index 0000000000..87de4398e1 --- /dev/null +++ b/engines/kyra/engine/kyra_v2.h @@ -0,0 +1,400 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_KYRA_V2_H +#define KYRA_KYRA_V2_H + +#include "kyra/kyra_v1.h" +#include "kyra/gui/gui_v1.h" +#include "kyra/graphics/wsamovie.h" +#include "kyra/engine/item.h" + +#include "common/list.h" +#include "common/hashmap.h" + +namespace Kyra { + +struct FrameControl { + uint16 index; + uint16 delay; +}; + +struct ItemAnimDefinition { + Item itemIndex; + uint8 numFrames; + const FrameControl *frames; +}; + +struct ActiveItemAnim { + uint16 currentFrame; + uint32 nextFrameTime; +}; + +class Screen_v2; + +class KyraEngine_v2 : public KyraEngine_v1 { +friend class Debugger_v2; +friend class GUI_v2; +public: + struct EngineDesc { + // Generic shape related + int itemShapeStart; + const uint8 *characterFrameTable; + + // Scene script + int firstAnimSceneScript; + + // Animation script specific + int animScriptFrameAdd; + + // Item specific + Item maxItemId; + }; + + KyraEngine_v2(OSystem *system, const GameFlags &flags, const EngineDesc &desc); + ~KyraEngine_v2(); + + virtual void pauseEngineIntern(bool pause); + + virtual Screen_v2 *screen_v2() const = 0; + + void delay(uint32 time, bool update = false, bool isMainLoop = false); + + const EngineDesc &engineDesc() const { return _desc; } +protected: + EngineDesc _desc; + + // run + uint32 _pauseStart; + bool _runFlag; + bool _showOutro; + + virtual void update() = 0; + virtual void updateWithText() = 0; + + // detection + int _lang; + + // Input + virtual int inputSceneChange(int x, int y, int unk1, int unk2) = 0; + + // Animator + struct AnimObj { + uint16 index; + uint16 type; + bool enabled; + uint16 needRefresh; + uint16 specialRefresh; + uint16 animFlags; + uint16 flags; + int16 xPos1, yPos1; + uint8 *shapePtr; + uint16 shapeIndex1; + uint16 animNum; + uint16 shapeIndex3; + uint16 shapeIndex2; + int16 xPos2, yPos2; + int16 xPos3, yPos3; + int16 width, height; + int16 width2, height2; + uint16 palette; + AnimObj *nextObject; + }; + + void allocAnimObjects(int actors, int anims, int items); + AnimObj *_animObjects; + + AnimObj *_animActor; + AnimObj *_animAnims; + AnimObj *_animItems; + + bool _drawNoShapeFlag; + AnimObj *_animList; + + AnimObj *initAnimList(AnimObj *list, AnimObj *entry); + AnimObj *addToAnimListSorted(AnimObj *list, AnimObj *entry); + AnimObj *deleteAnimListEntry(AnimObj *list, AnimObj *entry); + + virtual void refreshAnimObjects(int force) = 0; + void refreshAnimObjectsIfNeed(); + + void flagAnimObjsSpecialRefresh(); + void flagAnimObjsForRefresh(); + + virtual void clearAnimObjects() = 0; + + virtual void drawAnimObjects() = 0; + virtual void drawSceneAnimObject(AnimObj *obj, int x, int y, int drawLayer) = 0; + virtual void drawCharacterAnimObject(AnimObj *obj, int x, int y, int drawLayer) = 0; + + virtual void updateCharacterAnim(int) = 0; + virtual void updateSceneAnim(int anim, int newFrame) = 0; + + void addItemToAnimList(int item); + void deleteItemAnimEntry(int item); + + virtual void animSetupPaletteEntry(AnimObj *){} + + virtual void setCharacterAnimDim(int w, int h) = 0; + virtual void resetCharacterAnimDim() = 0; + + virtual int getScale(int x, int y) = 0; + + uint8 *_screenBuffer; + + // Scene + struct SceneDesc { + char filename1[10]; + char filename2[10]; + + uint16 exit1, exit2, exit3, exit4; + uint8 flags; + uint8 sound; + }; + + SceneDesc *_sceneList; + int _sceneListSize; + uint16 _currentScene; + + uint16 _sceneExit1, _sceneExit2, _sceneExit3, _sceneExit4; + int _sceneEnterX1, _sceneEnterY1, _sceneEnterX2, _sceneEnterY2, + _sceneEnterX3, _sceneEnterY3, _sceneEnterX4, _sceneEnterY4; + int _specialExitCount; + uint16 _specialExitTable[25]; + bool checkSpecialSceneExit(int num, int x, int y); + + bool _overwriteSceneFacing; + + virtual void enterNewScene(uint16 newScene, int facing, int unk1, int unk2, int unk3) = 0; + + void runSceneScript6(); + + EMCData _sceneScriptData; + EMCState _sceneScriptState; + + virtual int trySceneChange(int *moveTable, int unk1, int unk2) = 0; + + // Animation + virtual void restorePage3() = 0; + + struct SceneAnim { + uint16 flags; + int16 x, y; + int16 x2, y2; + int16 width, height; + uint16 specialSize; + int16 shapeIndex; + uint16 wsaFlag; + char filename[14]; + }; + + SceneAnim _sceneAnims[16]; + WSAMovie_v2 *_sceneAnimMovie[16]; + + void freeSceneAnims(); + + bool _specialSceneScriptState[10]; + bool _specialSceneScriptStateBackup[10]; + EMCState _sceneSpecialScripts[10]; + uint32 _sceneSpecialScriptsTimer[10]; + int _lastProcessedSceneScript; + bool _specialSceneScriptRunFlag; + + void updateSpecialSceneScripts(); + + // Sequences + EMCData _animationScriptData; + EMCState _animationScriptState; + Common::Array _opcodesAnimation; + + void runAnimationScript(const char *filename, int allowSkip, int resetChar, int newShapes, int shapeUnload); + + int o2a_setAnimationShapes(EMCState *script); + int o2a_setResetFrame(EMCState *script); + + char _animShapeFilename[14]; + + uint8 *_animShapeFiledata; + int _animShapeCount; + int _animShapeLastEntry; + + int _animNewFrame; + int _animDelayTime; + + int _animResetFrame; + + int _animShapeWidth, _animShapeHeight; + int _animShapeXAdd, _animShapeYAdd; + + bool _animNeedUpdate; + + virtual int initAnimationShapes(uint8 *filedata) = 0; + void processAnimationScript(int allowSkip, int resetChar); + virtual void uninitAnimationShapes(int count, uint8 *filedata) = 0; + + // Shapes + typedef Common::HashMap ShapeMap; + ShapeMap _gameShapes; + + uint8 *getShapePtr(int index) const; + void addShapeToPool(const uint8 *data, int realIndex, int shape); + void addShapeToPool(uint8 *shpData, int index); + void remShapeFromPool(int idx); + + int _characterShapeFile; + virtual void loadCharacterShapes(int shapes) = 0; + + // pathfinder + int _movFacingTable[600]; + int _pathfinderFlag; + bool _smoothingPath; + + int findWay(int curX, int curY, int dstX, int dstY, int *moveTable, int moveTableSize); + + bool directLinePassable(int x, int y, int toX, int toY); + + int pathfinderInitPositionTable(int *moveTable); + int pathfinderAddToPositionTable(int index, int v1, int v2); + int pathfinderInitPositionIndexTable(int tableLen, int x, int y); + int pathfinderAddToPositionIndexTable(int index, int v); + void pathfinderFinializePath(int *moveTable, int unk1, int x, int y, int moveTableSize); + + int _pathfinderPositionTable[400]; + int _pathfinderPositionIndexTable[200]; + + // items + struct ItemDefinition { + Item id; + uint16 sceneId; + int16 x; + uint8 y; + }; + + void initItemList(int size); + + Item _hiddenItems[100]; + + ItemDefinition *_itemList; + int _itemListSize; + + int _itemInHand; + int _savedMouseState; + + int findFreeItem(); + int countAllItems(); + + int findItem(uint16 sceneId, Item id); + int findItem(Item item); + + void resetItemList(); + void resetItem(int index); + + virtual void setMouseCursor(Item item) = 0; + + void setHandItem(Item item); + void removeHandItem(); + + // character + struct Character { + uint16 sceneId; + int16 dlgIndex; + uint8 height; + uint8 facing; + uint16 animFrame; + byte walkspeed; + Item inventory[20]; + int16 x1, y1; + int16 x2, y2; + int16 x3, y3; + }; + + Character _mainCharacter; + int _mainCharX, _mainCharY; + int _charScale; + + void moveCharacter(int facing, int x, int y); + int updateCharPos(int *table, int force = 0); + void updateCharPosWithUpdate(); + + uint32 _updateCharPosNextUpdate; + + virtual int getCharacterWalkspeed() const = 0; + virtual void updateCharAnimFrame(int *table) = 0; + + // chat + int _vocHigh; + + const char *_chatText; + int _chatObject; + uint32 _chatEndTime; + int _chatVocHigh, _chatVocLow; + bool _chatTextEnabled; + + EMCData _chatScriptData; + EMCState _chatScriptState; + + virtual void setDlgIndex(int dlgIndex) = 0; + + virtual void randomSceneChat() = 0; + + // unknown + int _unk4, _unk5; + bool _unkSceneScreenFlag1; + bool _unkHandleSceneChangeFlag; + + // opcodes + int o2_getCharacterX(EMCState *script); + int o2_getCharacterY(EMCState *script); + int o2_getCharacterFacing(EMCState *script); + int o2_getCharacterScene(EMCState *script); + int o2_setCharacterFacingOverwrite(EMCState *script); + int o2_trySceneChange(EMCState *script); + int o2_moveCharacter(EMCState *script); + int o2_checkForItem(EMCState *script); + int o2_defineItem(EMCState *script); + int o2_addSpecialExit(EMCState *script); + int o2_delay(EMCState *script); + int o2_update(EMCState *script); + int o2_getShapeFlag1(EMCState *script); + int o2_waitForConfirmationClick(EMCState *script); + int o2_randomSceneChat(EMCState *script); + int o2_setDlgIndex(EMCState *script); + int o2_getDlgIndex(EMCState *script); + int o2_defineRoomEntrance(EMCState *script); + int o2_runAnimationScript(EMCState *script); + int o2_setSpecialSceneScriptRunTime(EMCState *script); + int o2_defineScene(EMCState *script); + int o2_setSpecialSceneScriptState(EMCState *script); + int o2_clearSpecialSceneScriptState(EMCState *script); + int o2_querySpecialSceneScriptState(EMCState *script); + int o2_setHiddenItemsEntry(EMCState *script); + int o2_getHiddenItemsEntry(EMCState *script); + int o2_disableTimer(EMCState *script); + int o2_enableTimer(EMCState *script); + int o2_setTimerCountdown(EMCState *script); + int o2_setVocHigh(EMCState *script); + int o2_getVocHigh(EMCState *script); +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/lol.cpp b/engines/kyra/engine/lol.cpp new file mode 100644 index 0000000000..9cf045a876 --- /dev/null +++ b/engines/kyra/engine/lol.cpp @@ -0,0 +1,4513 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/engine/lol.h" +#include "kyra/graphics/screen_lol.h" +#include "kyra/resource/resource.h" +#include "kyra/engine/timer.h" +#include "kyra/engine/util.h" +#include "kyra/gui/debugger.h" +#include "kyra/sound/sound.h" + +#include "audio/audiostream.h" + +#include "common/config-manager.h" +#include "common/system.h" +#include "common/translation.h" + +#include "backends/keymapper/keymapper.h" + +namespace Kyra { + +const char *const LoLEngine::kKeymapName = "lol"; + +LoLEngine::LoLEngine(OSystem *system, const GameFlags &flags) : KyraRpgEngine(system, flags) { + _screen = 0; + _gui = 0; + _tim = 0; + + _lang = 0; + Common::Language lang = Common::parseLanguage(ConfMan.get("language")); + if (lang == _flags.fanLang && _flags.replacedLang != Common::UNK_LANG) + lang = _flags.replacedLang; + + switch (lang) { + case Common::EN_ANY: + case Common::EN_USA: + case Common::EN_GRB: + _lang = 0; + break; + + case Common::FR_FRA: + _lang = 1; + break; + + case Common::DE_DEU: + _lang = 2; + break; + + case Common::JA_JPN: + _lang = 0; + break; + + default: + warning("unsupported language, switching back to English"); + _lang = 0; + } + + _chargenFrameTable = _flags.isTalkie ? _chargenFrameTableTalkie : _chargenFrameTableFloppy; + _chargenWSA = 0; + _lastUsedStringBuffer = 0; + _landsFile = 0; + _levelLangFile = 0; + + _lastMusicTrack = -1; + _lastSfxTrack = -1; + _curTlkFile = -1; + _lastSpeaker = _lastSpeechId = _nextSpeechId = _nextSpeaker = -1; + + memset(_moneyColumnHeight, 0, sizeof(_moneyColumnHeight)); + _credits = 0; + + _itemsInPlay = 0; + _itemProperties = 0; + _itemInHand = 0; + memset(_inventory, 0, sizeof(_inventory)); + memset(_charStatusFlags, 0, sizeof(_charStatusFlags)); + _inventoryCurItem = 0; + _lastCharInventory = -1; + _emcLastItem = -1; + + _itemIconShapes = _itemShapes = _gameShapes = _thrownShapes = _effectShapes = _fireballShapes = _healShapes = _healiShapes = 0; + _levelShpList = _levelDatList = 0; + _gameShapeMap = 0; + memset(_monsterAnimType, 0, 3); + _healOverlay = 0; + _swarmSpellStatus = 0; + + _ingameMT32SoundIndex = _ingameGMSoundIndex = _ingamePCSpeakerSoundIndex = 0; + + _charSelection = -1; + _characters = 0; + _spellProperties = 0; + _selectedSpell = 0; + _updateCharNum = _portraitSpeechAnimMode = _textColorFlag = 0; + _palUpdateTimer = _updatePortraitNext = 0; + _lampStatusTimer = 0xFFFFFFFF; + + _weaponsDisabled = false; + _charInventoryUnk = 0; + _lastButtonShape = 0; + _buttonPressTimer = 0; + _selectedCharacter = 0; + _suspendScript = false; + _scriptDirection = 0; + _compassDirectionIndex = -1; + _compassStep = 0; + + _smoothScrollModeNormal = 1; + _wllAutomapData = 0; + _sceneXoffset = 112; + _sceneShpDim = 13; + _monsters = 0; + _monsterProperties = 0; + _lvlShapeIndex = 0; + _partyAwake = true; + _transparencyTable2 = 0; + _transparencyTable1 = 0; + _specialGuiShape = 0; + _specialGuiShapeX = _specialGuiShapeY = _specialGuiShapeMirrorFlag = 0; + memset(_characterFaceShapes, 0, sizeof(_characterFaceShapes)); + + _lampEffect = _brightness = _lampOilStatus = 0; + _lampStatusSuspended = false; + _tempBuffer5120 = 0; + _flyingObjects = 0; + _monsters = 0; + _lastMouseRegion = 0; + _objectLastDirection = 0; + _monsterCurBlock = 0; + _seqWindowX1 = _seqWindowY1 = _seqWindowX2 = _seqWindowY2 = _seqTrigger = 0; + _spsWindowX = _spsWindowY = _spsWindowW = _spsWindowH = 0; + + _dscWalls = 0; + _dscOvlMap = 0; + _dscShapeScaleW = 0; + _dscShapeScaleH = 0; + _dscShapeY = 0; + _dscShapeOvlIndex = 0; + _dscDoorMonsterX = _dscDoorMonsterY = 0; + _dscDoor4 = 0; + + _ingameSoundList = 0; + _ingameSoundIndex = 0; + _ingameSoundListSize = 0; + _musicTrackMap = 0; + _curMusicTheme = -1; + _curMusicFileExt = 0; + _curMusicFileIndex = -1; + _envSfxUseQueue = false; + _envSfxNumTracksInQueue = 0; + memset(_envSfxQueuedTracks, 0, sizeof(_envSfxQueuedTracks)); + memset(_envSfxQueuedBlocks, 0, sizeof(_envSfxQueuedBlocks)); + + _partyPosX = _partyPosY = 0; + _shpDmX = _shpDmY = _dmScaleW = _dmScaleH = 0; + + _floatingCursorControl = _currentFloatingCursor = 0; + + memset(_activeTim, 0, sizeof(_activeTim)); + memset(&_activeSpell, 0, sizeof(_activeSpell)); + + _pageBuffer1 = _pageBuffer2 = 0; + + memset(_charStatsTemp, 0, sizeof(_charStatsTemp)); + + _compassBroken = _drainMagic = 0; + + _buttonData = 0; + _preserveEvents = false; + _buttonList1 = _buttonList2 = _buttonList3 = _buttonList4 = _buttonList5 = _buttonList6 = _buttonList7 = _buttonList8 = 0; + + _mapOverlay = 0; + _automapShapes = 0; + _defaultLegendData = 0; + _mapCursorOverlay = 0; + + _lightningProps = 0; + _lightningCurSfx = -1; + _lightningDiv = 0; + _lightningFirstSfx = 0; + _lightningSfxFrame = 0; + + _compassTimer = 0; + _scriptCharacterCycle = 0; + _partyDamageFlags = -1; + + memset(&_itemScript, 0, sizeof(_itemScript)); +} + +LoLEngine::~LoLEngine() { + setupPrologueData(false); + releaseTempData(); + + delete[] _landsFile; + delete[] _levelLangFile; + + delete _screen; + _screen = 0; + delete _gui; + _gui = 0; + delete _tim; + _tim = 0; + delete _txt; + _txt = 0; + + delete[] _itemsInPlay; + delete[] _itemProperties; + delete[] _characters; + + delete[] _pageBuffer1; + delete[] _pageBuffer2; + + if (_itemIconShapes) { + for (int i = 0; i < _numItemIconShapes; i++) + delete[] _itemIconShapes[i]; + delete[] _itemIconShapes; + } + + if (_itemShapes) { + for (int i = 0; i < _numItemShapes; i++) + delete[] _itemShapes[i]; + delete[] _itemShapes; + } + + if (_gameShapes) { + for (int i = 0; i < _numGameShapes; i++) + delete[] _gameShapes[i]; + delete[] _gameShapes; + } + + if (_thrownShapes) { + for (int i = 0; i < _numThrownShapes; i++) + delete[] _thrownShapes[i]; + delete[] _thrownShapes; + } + + if (_effectShapes) { + for (int i = 0; i < _numEffectShapes; i++) + delete[] _effectShapes[i]; + delete[] _effectShapes; + } + + if (_fireballShapes) { + for (int i = 0; i < _numFireballShapes; i++) + delete[] _fireballShapes[i]; + delete[] _fireballShapes; + } + + if (_healShapes) { + for (int i = 0; i < _numHealShapes; i++) + delete[] _healShapes[i]; + delete[] _healShapes; + } + + if (_healiShapes) { + for (int i = 0; i < _numHealiShapes; i++) + delete[] _healiShapes[i]; + delete[] _healiShapes; + } + + if (_monsterDecorationShapes) { + for (int i = 0; i < 3; i++) + releaseMonsterShapes(i); + + delete[] _monsterShapes; + _monsterShapes = 0; + delete[] _monsterPalettes; + _monsterPalettes = 0; + delete[] _monsterDecorationShapes; + _monsterDecorationShapes = 0; + } + + for (int i = 0; i < 6; i++) { + delete[] _doorShapes[i]; + _doorShapes[i] = 0; + } + + releaseDecorations(); + + delete[] _automapShapes; + + for (Common::Array::iterator i = _timIntroOpcodes.begin(); i != _timIntroOpcodes.end(); ++i) + delete *i; + _timIntroOpcodes.clear(); + + for (Common::Array::iterator i = _timOutroOpcodes.begin(); i != _timOutroOpcodes.end(); ++i) + delete *i; + _timOutroOpcodes.clear(); + + for (Common::Array::iterator i = _timIngameOpcodes.begin(); i != _timIngameOpcodes.end(); ++i) + delete *i; + _timIngameOpcodes.clear(); + + delete[] _wllAutomapData; + delete[] _tempBuffer5120; + delete[] _flyingObjects; + delete[] _monsters; + delete[] _monsterProperties; + + delete[] _transparencyTable2; + delete[] _transparencyTable1; + delete[] _lightningProps; + + delete _lvlShpFileHandle; + + if (_ingameSoundList) { + for (int i = 0; i < _ingameSoundListSize; i++) + delete[] _ingameSoundList[i]; + delete[] _ingameSoundList; + } + + for (int i = 0; i < 3; i++) { + for (int ii = 0; ii < 40; ii++) + delete[] _characterFaceShapes[ii][i]; + } + + delete[] _healOverlay; + + delete[] _defaultLegendData; + delete[] _mapCursorOverlay; + delete[] _mapOverlay; + + for (Common::Array::iterator i = _spellProcs.begin(); i != _spellProcs.end(); ++i) + delete *i; + _spellProcs.clear(); + + for (SpeechList::iterator i = _speechList.begin(); i != _speechList.end(); ++i) + delete *i; + _speechList.clear(); + + _emc->unload(&_itemScript); + _emc->unload(&_scriptData); +} + +Screen *LoLEngine::screen() { + return _screen; +} + +GUI *LoLEngine::gui() const { + return _gui; +} + +Common::Error LoLEngine::init() { + _screen = new Screen_LoL(this, _system); + assert(_screen); + _screen->setResolution(); + + _debugger = new Debugger_LoL(this); + assert(_debugger); + + KyraEngine_v1::init(); + initStaticResource(); + + _gui = new GUI_LoL(this); + assert(_gui); + _gui->initStaticData(); + + _txt = new TextDisplayer_LoL(this, _screen); + + _screen->setAnimBlockPtr(10000); + _screen->setScreenDim(0); + + _pageBuffer1 = new uint8[0xFA00]; + memset(_pageBuffer1, 0, 0xFA00); + _pageBuffer2 = new uint8[0xFA00]; + memset(_pageBuffer2, 0, 0xFA00); + + _itemsInPlay = new LoLItem[400]; + memset(_itemsInPlay, 0, sizeof(LoLItem) * 400); + + _characters = new LoLCharacter[4]; + memset(_characters, 0, sizeof(LoLCharacter) * 4); + + if (!_sound->init()) + error("Couldn't init sound"); + + KyraRpgEngine::init(); + + _wllAutomapData = new uint8[80]; + memset(_wllAutomapData, 0, 80); + + _monsters = new LoLMonster[30]; + memset(_monsters, 0, 30 * sizeof(LoLMonster)); + _monsterProperties = new LoLMonsterProperty[5]; + memset(_monsterProperties, 0, 5 * sizeof(LoLMonsterProperty)); + + _tempBuffer5120 = new uint8[5120]; + memset(_tempBuffer5120, 0, 5120); + + _flyingObjects = new FlyingObject[_numFlyingObjects]; + _flyingObjectsPtr = _flyingObjects; + _flyingObjectStructSize = sizeof(FlyingObject); + memset(_flyingObjects, 0, _numFlyingObjects * sizeof(FlyingObject)); + + memset(_globalScriptVars, 0, sizeof(_globalScriptVars)); + + _lvlShpFileHandle = 0; + + _sceneDrawPage1 = 2; + _sceneDrawPage2 = 6; + + _clickedShapeXOffs = 136; + _clickedShapeYOffs = 8; + _clickedSpecialFlag = 0x40; + + _monsterShapes = new uint8*[48]; + memset(_monsterShapes, 0, 48 * sizeof(uint8 *)); + _monsterPalettes = new uint8*[48]; + memset(_monsterPalettes, 0, 48 * sizeof(uint8 *)); + _monsterDecorationShapes = new uint8*[576]; + memset(_monsterDecorationShapes, 0, 576 * sizeof(uint8 *)); + memset(&_scriptData, 0, sizeof(EMCData)); + + _activeMagicMenu = -1; + + _automapShapes = new const uint8*[109]; + _mapOverlay = new uint8[256]; + + memset(_availableSpells, -1, 8); + + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castSpark)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castHeal)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castIce)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castFireball)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castHandOfFate)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castMistOfDoom)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castLightning)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castFog)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castSwarm)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castVaelansCube)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, 0)); + _spellProcs.push_back(new SpellProc(this, &LoLEngine::castGuardian)); + +#ifdef ENABLE_KEYMAPPER + _eventMan->getKeymapper()->pushKeymap(kKeymapName, true); +#endif + + return Common::kNoError; +} + +void LoLEngine::initKeymap() { +#ifdef ENABLE_KEYMAPPER + Common::Keymapper *const mapper = _eventMan->getKeymapper(); + + // Do not try to recreate same keymap over again + if (mapper->getKeymap(kKeymapName) != 0) + return; + + Common::Keymap *const engineKeyMap = new Common::Keymap(kKeymapName); + + const Common::KeyActionEntry keyActionEntries[] = { + {Common::KeyState(Common::KEYCODE_F1, Common::ASCII_F1), "AT1", _("Attack 1")}, + {Common::KeyState(Common::KEYCODE_F2, Common::ASCII_F2), "AT2", _("Attack 2")}, + {Common::KeyState(Common::KEYCODE_F3, Common::ASCII_F3), "AT3", _("Attack 3")}, + {Common::KeyState(Common::KEYCODE_UP), "MVF", _("Move Forward")}, + {Common::KeyState(Common::KEYCODE_DOWN), "MVB", _("Move Back")}, + {Common::KeyState(Common::KEYCODE_LEFT), "SLL", _("Slide Left")}, + {Common::KeyState(Common::KEYCODE_RIGHT), "SLR", _("Slide Right")}, + {Common::KeyState(Common::KEYCODE_HOME), "TL", _("Turn Left")}, + {Common::KeyState(Common::KEYCODE_PAGEUP), "TR", _("Turn Right")}, + {Common::KeyState(Common::KEYCODE_r), "RST", _("Rest")}, + {Common::KeyState(Common::KEYCODE_o), "OPT", _("Options")}, + {Common::KeyState(Common::KEYCODE_SLASH), "SPL", _("Choose Spell")}, + {Common::KeyState(), 0, 0} + }; + + for (const Common::KeyActionEntry *entry = keyActionEntries; entry->id; ++entry) { + Common::Action *const act = new Common::Action(engineKeyMap, entry->id, entry->description); + act->addKeyEvent(entry->ks); + } + + mapper->addGameKeymap(engineKeyMap); +#endif +} + +void LoLEngine::pauseEngineIntern(bool pause) { + KyraEngine_v1::pauseEngineIntern(pause); + pauseDemoPlayer(pause); +} + +Common::Error LoLEngine::go() { + int action = -1; + + if (_gameToLoad == -1) { + action = processPrologue(); + if (action == -1) + return Common::kNoError; + } + + if (_flags.isTalkie && !_flags.isDemo) { + if (!_res->loadFileList("FILEDATA.FDT")) + error("Couldn't load file list: 'FILEDATA.FDT'"); + } else if (_pakFileList) { + _res->loadFileList(_pakFileList, _pakFileListSize); + } + + // Usually fonts etc. would be setup by the prologue code, if we skip + // the prologue code we need to setup them manually here. + if (_gameToLoad != -1 && action != 3) { + preInit(); + _screen->setFont((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? Screen::FID_SJIS_FNT : Screen::FID_9_FNT); + } + + // We have three sound.dat files, one for the intro, one for the + // end sequence and one for ingame, each contained in a different + // PAK file. Therefore a new call to loadSoundFile() is required + // whenever the PAK file configuration changes. + if (_flags.platform == Common::kPlatformPC98) + _sound->loadSoundFile("sound.dat"); + + _sound->selectAudioResourceSet(kMusicIngame); + if (_flags.platform != Common::kPlatformDOS) + _sound->loadSoundFile(0); + + _tim = new TIMInterpreter_LoL(this, _screen, _system); + assert(_tim); + + if (shouldQuit()) + return Common::kNoError; + + startup(); + + if (action == 0) { + startupNew(); + } else if (_gameToLoad != -1) { + // FIXME: Instead of throwing away the error returned by + // loadGameState, we should use it / augment it. + if (loadGameState(_gameToLoad).getCode() != Common::kNoError) + error("Couldn't load game slot %d on startup", _gameToLoad); + _gameToLoad = -1; + } + + _screen->_fadeFlag = 3; + _sceneUpdateRequired = true; + enableSysTimer(1); + runLoop(); + + return Common::kNoError; +} + +#pragma mark - Initialization + +void LoLEngine::preInit() { + _res->loadPakFile("GENERAL.PAK"); + if (_flags.isTalkie) + _res->loadPakFile("STARTUP.PAK"); + + _screen->loadFont(Screen::FID_9_FNT, "FONT9P.FNT"); + _screen->loadFont(Screen::FID_6_FNT, "FONT6P.FNT"); + + loadTalkFile(0); + + Common::String filename; + filename = Common::String::format("LANDS.%s", _languageExt[_lang]); + _res->exists(filename.c_str(), true); + delete[] _landsFile; + _landsFile = _res->fileData(filename.c_str(), 0); + loadItemIconShapes(); +} + +void LoLEngine::loadItemIconShapes() { + if (_itemIconShapes) { + for (int i = 0; i < _numItemIconShapes; i++) + delete[] _itemIconShapes[i]; + delete[] _itemIconShapes; + } + + _screen->loadBitmap("ITEMICN.SHP", 3, 3, 0); + const uint8 *shp = _screen->getCPagePtr(3); + _numItemIconShapes = READ_LE_UINT16(shp); + _itemIconShapes = new uint8*[_numItemIconShapes]; + for (int i = 0; i < _numItemIconShapes; i++) + _itemIconShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->setMouseCursor(0, 0, _itemIconShapes[0]); + + if (!_gameShapes) { + _screen->loadBitmap("GAMESHP.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numGameShapes = READ_LE_UINT16(shp); + _gameShapes = new uint8*[_numGameShapes]; + for (int i = 0; i < _numGameShapes; i++) + _gameShapes[i] = _screen->makeShapeCopy(shp, i); + } +} + +void LoLEngine::setMouseCursorToIcon(int icon) { + _flagsTable[31] |= 0x02; + int i = _itemProperties[_itemsInPlay[_itemInHand].itemPropertyIndex].shpIndex; + if (i == icon) + return; + _screen->setMouseCursor(0, 0, _itemIconShapes[icon]); +} + +void LoLEngine::setMouseCursorToItemInHand() { + _flagsTable[31] &= 0xFD; + int o = (_itemInHand == 0) ? 0 : 10; + _screen->setMouseCursor(o, o, getItemIconShapePtr(_itemInHand)); +} + +void LoLEngine::checkFloatingPointerRegions() { + if (!_floatingCursorsEnabled) + return; + + int t = -1; + + Common::Point p = getMousePos(); + + if (!(_updateFlags & 4) & !_floatingCursorControl) { + if (posWithinRect(p.x, p.y, 96, 0, 303, 136)) { + if (!posWithinRect(p.x, p.y, 128, 16, 271, 119)) { + if (posWithinRect(p.x, p.y, 112, 0, 287, 15)) + t = 0; + if (posWithinRect(p.x, p.y, 272, 88, 303, 319)) + t = 1; + if (posWithinRect(p.x, p.y, 112, 110, 287, 135)) + t = 2; + if (posWithinRect(p.x, p.y, 96, 88, 127, 119)) + t = 3; + if (posWithinRect(p.x, p.y, 96, 16, 127, 87)) + t = 4; + if (posWithinRect(p.x, p.y, 272, 16, 303, 87)) + t = 5; + + if (t < 4) { + int d = (_currentDirection + t) & 3; + if (!checkBlockPassability(calcNewBlockPosition(_currentBlock, d), d)) + t = 6; + } + } + } + } + + if (t == _currentFloatingCursor) + return; + + if (t == -1) { + setMouseCursorToItemInHand(); + } else { + static const uint8 floatingPtrX[] = { 7, 13, 7, 0, 0, 15, 7 }; + static const uint8 floatingPtrY[] = { 0, 7, 12, 7, 6, 6, 7 }; + _screen->setMouseCursor(floatingPtrX[t], floatingPtrY[t], _gameShapes[10 + t]); + } + + _currentFloatingCursor = t; +} + +uint8 *LoLEngine::getItemIconShapePtr(int index) { + int ix = _itemProperties[_itemsInPlay[index].itemPropertyIndex].shpIndex; + if (_itemProperties[_itemsInPlay[index].itemPropertyIndex].flags & 0x200) + ix += (_itemsInPlay[index].shpCurFrame_flg & 0x1FFF) - 1; + + return _itemIconShapes[ix]; +} + +int LoLEngine::mainMenu() { + bool hasSave = saveFileLoadable(0); + + MainMenu::StaticData data[] = { + // 256 color ASCII mode + { + { 0, 0, 0, 0, 0 }, + { 0x01, 0x04, 0x0C, 0x04, 0x00, 0x3D, 0x9F }, + { 0x2C, 0x19, 0x48, 0x2C }, + Screen::FID_9_FNT, 1 + }, + // 16 color SJIS mode + { + { 0, 0, 0, 0, 0 }, + { 0x01, 0x04, 0x0C, 0x04, 0x00, 0xC1, 0xE1 }, + { 0xCC, 0xDD, 0xDD, 0xDD }, + Screen::FID_SJIS_FNT, 1 + } + }; + + int dataIndex = _flags.use16ColorMode ? 1 : 0; + + if (!_flags.isTalkie) + --data[dataIndex].menuTable[3]; + + if (hasSave) + ++data[dataIndex].menuTable[3]; + + static const uint16 mainMenuStrings[4][5] = { + { 0x4248, 0x4249, 0x42DD, 0x424A, 0x0000 }, + { 0x4248, 0x4249, 0x42DD, 0x4001, 0x424A }, + { 0x4248, 0x4249, 0x424A, 0x0000, 0x0000 }, + { 0x4248, 0x4249, 0x4001, 0x424A, 0x0000 } + }; + + int tableOffs = _flags.isTalkie ? 0 : 2; + + for (int i = 0; i < 5; ++i) { + if (hasSave) + data[dataIndex].strings[i] = getLangString(mainMenuStrings[1 + tableOffs][i]); + else + data[dataIndex].strings[i] = getLangString(mainMenuStrings[tableOffs][i]); + } + + MainMenu *menu = new MainMenu(this); + assert(menu); + menu->init(data[dataIndex], MainMenu::Animation()); + + int selection = menu->handle(_flags.isTalkie ? (hasSave ? 19 : 6) : (hasSave ? 6 : 20)); + delete menu; + _screen->setScreenDim(0); + + if (!_flags.isTalkie && selection >= 2) + selection++; + + if (!hasSave && selection == 3) + selection = 4; + + return selection; +} + +void LoLEngine::startup() { + _screen->clearPage(0); + + Palette &pal = _screen->getPalette(0); + _screen->loadBitmap("PLAYFLD.CPS", 3, 3, &pal); + + if (_flags.use16ColorMode) { + memset(_screen->_paletteOverlay1, 0, 256); + memset(_screen->_paletteOverlay2, 0, 256); + + static const uint8 colTable1[] = { 0x00, 0xEE, 0xCC, 0xFF, 0x44, 0x66, 0x44, 0x88, 0xEE, 0xAA, 0x11, 0xCC, 0xDD, 0xEE, 0x44, 0xCC }; + static const uint8 colTable2[] = { 0x00, 0xCC, 0xFF, 0xBB, 0xEE, 0xBB, 0x55, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xFF, 0xCC, 0xDD, 0xBB }; + static const uint8 colTable3[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; + + for (int i = 0; i < 16; i++) { + _screen->_paletteOverlay1[colTable3[i]] = colTable1[i]; + _screen->_paletteOverlay2[colTable3[i]] = colTable2[i]; + } + + } else { + _screen->copyPalette(1, 0); + pal.fill(0, 1, 0x3F); + pal.fill(2, 126, 0x3F); + pal.fill(192, 4, 0x3F); + _screen->generateOverlay(pal, _screen->_paletteOverlay1, 1, 96, 254); + _screen->generateOverlay(pal, _screen->_paletteOverlay2, 144, 65, 254); + _screen->copyPalette(0, 1); + } + + _screen->getPalette(1).clear(); + _screen->getPalette(2).clear(); + + loadItemIconShapes(); + _screen->setMouseCursor(0, 0, _itemIconShapes[0x85]); + + _screen->loadBitmap("ITEMSHP.SHP", 3, 3, 0); + const uint8 *shp = _screen->getCPagePtr(3); + _numItemShapes = READ_LE_UINT16(shp); + _itemShapes = new uint8*[_numItemShapes]; + for (int i = 0; i < _numItemShapes; i++) + _itemShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->loadBitmap("THROWN.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numThrownShapes = READ_LE_UINT16(shp); + _thrownShapes = new uint8*[_numThrownShapes]; + for (int i = 0; i < _numThrownShapes; i++) + _thrownShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->loadBitmap("ICE.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numEffectShapes = READ_LE_UINT16(shp); + _effectShapes = new uint8*[_numEffectShapes]; + for (int i = 0; i < _numEffectShapes; i++) + _effectShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->loadBitmap("FIREBALL.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numFireballShapes = READ_LE_UINT16(shp); + _fireballShapes = new uint8*[_numFireballShapes]; + for (int i = 0; i < _numFireballShapes; i++) + _fireballShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->loadBitmap("HEAL.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numHealShapes = READ_LE_UINT16(shp); + _healShapes = new uint8*[_numHealShapes]; + for (int i = 0; i < _numHealShapes; i++) + _healShapes[i] = _screen->makeShapeCopy(shp, i); + + _screen->loadBitmap("HEALI.SHP", 3, 3, 0); + shp = _screen->getCPagePtr(3); + _numHealiShapes = READ_LE_UINT16(shp); + _healiShapes = new uint8*[_numHealiShapes]; + for (int i = 0; i < _numHealiShapes; i++) + _healiShapes[i] = _screen->makeShapeCopy(shp, i); + + memset(_itemsInPlay, 0, 400 * sizeof(LoLItem)); + for (int i = 0; i < 400; i++) + _itemsInPlay[i].shpCurFrame_flg |= 0x8000; + + runInitScript("ONETIME.INF", 0); + _emc->load("ITEM.INF", &_itemScript, &_opcodes); + + _transparencyTable1 = new uint8[256]; + _transparencyTable2 = new uint8[5120]; + + _loadSuppFilesFlag = 1; + + _sound->loadSfxFile("LORESFX"); + + setMouseCursorToItemInHand(); +} + +void LoLEngine::startupNew() { + _selectedSpell = 0; + _compassStep = 0; + _compassDirection = _compassDirectionIndex = -1; + + _lastMouseRegion = -1; + _currentLevel = 1; + + giveCredits(41, 0); + _inventory[0] = makeItem(216, 0, 0); + _inventory[1] = makeItem(217, 0, 0); + _inventory[2] = makeItem(218, 0, 0); + + _availableSpells[0] = 0; + setupScreenDims(); + + Common::fill(_globalScriptVars2, ARRAYEND(_globalScriptVars2), 0x100); + + static const int selectIds[] = { -9, -1, -8, -5 }; + assert(_charSelection >= 0); + assert(_charSelection < ARRAYSIZE(selectIds)); + addCharacter(selectIds[_charSelection]); + + gui_enableDefaultPlayfieldButtons(); + + loadLevel(_currentLevel); + + _screen->showMouse(); +} + +void LoLEngine::runLoop() { + // Initialize debugger since how it should be fully usable + _debugger->initialize(); + + enableSysTimer(2); + + _flagsTable[73] |= 0x08; + + while (!shouldQuit()) { + if (_gameToLoad != -1) { + // FIXME: Instead of throwing away the error returned by + // loadGameState, we should use it / augment it. + if (loadGameState(_gameToLoad).getCode() != Common::kNoError) + error("Couldn't load game slot %d", _gameToLoad); + _gameToLoad = -1; + } + + if (_nextScriptFunc) { + runLevelScript(_nextScriptFunc, 2); + _nextScriptFunc = 0; + } + + _timer->update(); + + checkFloatingPointerRegions(); + gui_updateInput(); + + update(); + + if (_sceneUpdateRequired) + gui_drawScene(0); + else + updateEnvironmentalSfx(0); + + if (_partyDamageFlags != -1) { + checkForPartyDeath(); + _partyDamageFlags = -1; + } + + delay(_tickLength); + } +} + +void LoLEngine::registerDefaultSettings() { + KyraEngine_v1::registerDefaultSettings(); + + // Most settings already have sensible defaults. This one, however, is + // specific to the LoL engine. + ConfMan.registerDefault("floating_cursors", false); + ConfMan.registerDefault("smooth_scrolling", true); + ConfMan.registerDefault("monster_difficulty", 1); +} + +void LoLEngine::writeSettings() { + ConfMan.setInt("monster_difficulty", _monsterDifficulty); + ConfMan.setBool("floating_cursors", _floatingCursorsEnabled); + ConfMan.setBool("smooth_scrolling", _smoothScrollingEnabled); + + switch (_lang) { + case 1: + _flags.lang = Common::FR_FRA; + break; + + case 2: + _flags.lang = Common::DE_DEU; + break; + + case 0: + default: + if (_flags.platform == Common::kPlatformPC98 || _flags.platform == Common::kPlatformFMTowns) + _flags.lang = Common::JA_JPN; + else + _flags.lang = Common::EN_ANY; + } + + if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG) + _flags.lang = _flags.fanLang; + + ConfMan.set("language", Common::getLanguageCode(_flags.lang)); + + KyraEngine_v1::writeSettings(); +} + +void LoLEngine::readSettings() { + _monsterDifficulty = ConfMan.getInt("monster_difficulty"); + if (_monsterDifficulty < 0 || _monsterDifficulty > 2) { + _monsterDifficulty = CLIP(_monsterDifficulty, 0, 2); + warning("LoLEngine: Config file contains invalid difficulty setting."); + } + _smoothScrollingEnabled = ConfMan.getBool("smooth_scrolling"); + _floatingCursorsEnabled = ConfMan.getBool("floating_cursors"); + + KyraEngine_v1::readSettings(); +} + +void LoLEngine::update() { + updateSequenceBackgroundAnimations(); + + if (_updateCharNum != -1 && _system->getMillis() > _updatePortraitNext) + updatePortraitSpeechAnim(); + + if (_flagsTable[31] & 0x08 || !(_updateFlags & 4)) + updateLampStatus(); + + if (_flagsTable[31] & 0x40 && !(_updateFlags & 4) && (_compassDirection == -1 || (_currentDirection << 6) != _compassDirection || _compassStep)) + updateCompass(); + + snd_updateCharacterSpeech(); + fadeText(); + + updateInput(); + _screen->updateScreen(); +} + +#pragma mark - Localization + +char *LoLEngine::getLangString(uint16 id) { + if (id == 0xFFFF) + return 0; + + uint16 realId = id & 0x3FFF; + uint8 *buffer = 0; + + if (id & 0x4000) + buffer = _landsFile; + else + buffer = _levelLangFile; + + if (!buffer) + return 0; + + char *string = (char *)getTableEntry(buffer, realId); + + char *srcBuffer = _stringBuffer[_lastUsedStringBuffer]; + if (_flags.lang == Common::JA_JPN) { + decodeSjis(string, srcBuffer); + } else if (_flags.lang == Common::RU_RUS && !_flags.isTalkie) { + decodeCyrillic(string, srcBuffer); + Util::decodeString2(srcBuffer, srcBuffer); + } else { + Util::decodeString1(string, srcBuffer); + Util::decodeString2(srcBuffer, srcBuffer); + } + + ++_lastUsedStringBuffer; + _lastUsedStringBuffer %= ARRAYSIZE(_stringBuffer); + + return srcBuffer; +} + +uint8 *LoLEngine::getTableEntry(uint8 *buffer, uint16 id) { + if (!buffer) + return 0; + + return buffer + READ_LE_UINT16(buffer + (id << 1)); +} + +void LoLEngine::decodeSjis(const char *src, char *dst) { + char s[2]; + char d[3]; + s[1] = 0; + + uint8 cmd = 0; + while ((cmd = *src++) != 0) { + if (cmd == 27) { + cmd = *src++ & 0x7F; + memcpy(dst, src, cmd * 2); + dst += cmd * 2; + src += cmd * 2; + } else { + s[0] = cmd; + int size = Util::decodeString1(s, d); + memcpy(dst, d, size); + dst += size; + } + } + + *dst = 0; +} + +int LoLEngine::decodeCyrillic(const char *src, char *dst) { + static const uint8 decodeTable1[] = { + 0x20, 0xAE, 0xA5, 0xA0, 0xE2, 0xAD, 0xA8, 0xE0, 0xE1, 0xAB, 0xA2, + 0xA4, 0xAC, 0xAA, 0xE3, 0x2E + }; + + static const uint8 decodeTable2[] = { + 0xAD, 0xAF, 0xA2, 0xE1, 0xAC, 0xAA, 0x20, 0xA4, 0xAB, 0x20, + 0xE0, 0xE2, 0xA4, 0xA2, 0xA6, 0xAA, 0x20, 0xAD, 0xE2, 0xE0, + 0xAB, 0xAC, 0xE1, 0xA1, 0x20, 0xAC, 0xE1, 0xAA, 0xAB, 0xE0, + 0xE2, 0xAD, 0xAE, 0xEC, 0xA8, 0xA5, 0xA0, 0x20, 0xE0, 0xEB, + 0xAE, 0xA0, 0xA8, 0xA5, 0xEB, 0xEF, 0x20, 0xE3, 0xE2, 0x20, + 0xAD, 0xE7, 0xAB, 0xAC, 0xA5, 0xE0, 0xAE, 0xA0, 0xA5, 0xA8, + 0xE3, 0xEB, 0xEF, 0xAA, 0xE2, 0xEF, 0xA5, 0xEC, 0xAB, 0xAE, + 0xAA, 0xAF, 0xA8, 0xA0, 0xA5, 0xEF, 0xAE, 0xEE, 0xEC, 0xE3, + 0xA0, 0xAE, 0xA5, 0xA8, 0xEB, 0x20, 0xE0, 0xE3, 0xA0, 0xA5, + 0xAE, 0xA8, 0xE3, 0xE1, 0xAD, 0xAB, 0x20, 0xAE, 0xA5, 0xA0, + 0xA8, 0xAD, 0x2E, 0xE3, 0xAE, 0xA0, 0xA8, 0x20, 0xE0, 0xE3, + 0xAB, 0xE1, 0x20, 0xA4, 0xAD, 0xE2, 0xA1, 0xA6, 0xAC, 0xE1, + 0x0D, 0x20, 0x2E, 0x09, 0xA0, 0xA1, 0x9D, 0xA5 + }; + + int size = 0; + uint cChar = 0; + while ((cChar = *src++) != 0) { + if (cChar & 0x80) { + cChar &= 0x7F; + int index = (cChar & 0x78) >> 3; + *dst++ = decodeTable1[index]; + ++size; + assert(cChar < sizeof(decodeTable2)); + cChar = decodeTable2[cChar]; + } else if (cChar >= 0x70) { + cChar = *src++; + } else if (cChar >= 0x30) { + if (cChar < 0x60) + cChar -= 0x30; + cChar |= 0x80; + } + + *dst++ = cChar; + ++size; + } + + *dst++ = 0; + return size; +} + +bool LoLEngine::addCharacter(int id) { + const uint16 *cdf[] = { + _charDefsMan, _charDefsMan, _charDefsMan, _charDefsWoman, + _charDefsMan, _charDefsMan, _charDefsWoman, _charDefsKieran, _charDefsAkshel + }; + + int numChars = countActiveCharacters(); + if (numChars == 4) + return false; + + int i = 0; + for (; i < _charDefaultsSize; i++) { + if (_charDefaults[i].id == id) { + memcpy(&_characters[numChars], &_charDefaults[i], sizeof(LoLCharacter)); + _characters[numChars].defaultModifiers = cdf[i]; + break; + } + } + if (i == _charDefaultsSize) + return false; + + loadCharFaceShapes(numChars, id); + + _characters[numChars].nextAnimUpdateCountdown = rollDice(1, 12) + 6; + + for (i = 0; i < 11; i++) { + if (_characters[numChars].items[i]) { + _characters[numChars].items[i] = makeItem(_characters[numChars].items[i], 0, 0); + runItemScript(numChars, _characters[numChars].items[i], 0x80, 0, 0); + } + } + + calcCharPortraitXpos(); + if (numChars > 0) + setTemporaryFaceFrame(numChars, 2, 6, 0); + + return true; +} + +void LoLEngine::setTemporaryFaceFrame(int charNum, int frame, int updateDelay, int redraw) { + _characters[charNum].tempFaceFrame = frame; + if (frame || updateDelay) + setCharacterUpdateEvent(charNum, 6, updateDelay, 1); + if (redraw) + gui_drawCharPortraitWithStats(charNum); +} + +void LoLEngine::setTemporaryFaceFrameForAllCharacters(int frame, int updateDelay, int redraw) { + for (int i = 0; i < 4; i++) + setTemporaryFaceFrame(i, frame, updateDelay, 0); + if (redraw) + gui_drawAllCharPortraitsWithStats(); +} + +void LoLEngine::setCharacterUpdateEvent(int charNum, int updateType, int updateDelay, int overwrite) { + LoLCharacter *l = &_characters[charNum]; + for (int i = 0; i < 5; i++) { + if (l->characterUpdateEvents[i] && (!overwrite || l->characterUpdateEvents[i] != updateType)) + continue; + + l->characterUpdateEvents[i] = updateType; + l->characterUpdateDelay[i] = updateDelay; + _timer->setNextRun(3, _system->getMillis()); + _timer->resetNextRun(); + _timer->enable(3); + break; + } +} + +int LoLEngine::countActiveCharacters() { + int i = 0; + while (_characters[i].flags & 1) + i++; + return i; +} + +void LoLEngine::loadCharFaceShapes(int charNum, int id) { + if (id < 0) + id = -id; + + Common::String file = Common::String::format("FACE%02d.SHP", id); + _screen->loadBitmap(file.c_str(), 3, 3, 0); + + const uint8 *p = _screen->getCPagePtr(3); + for (int i = 0; i < 40; i++) { + delete[] _characterFaceShapes[i][charNum]; + _characterFaceShapes[i][charNum] = _screen->makeShapeCopy(p, i); + } +} + +void LoLEngine::updatePortraitSpeechAnim() { + int x = 0; + int y = 0; + bool redraw = false; + + if (_portraitSpeechAnimMode == 0) { + x = _activeCharsXpos[_updateCharNum]; + y = 144; + redraw = true; + } else if (_portraitSpeechAnimMode == 1) { + if (textEnabled()) { + x = 90; + y = 130; + } else { + x = _activeCharsXpos[_updateCharNum]; + y = 144; + } + } else if (_portraitSpeechAnimMode == 2) { + if (textEnabled()) { + x = 16; + y = 134; + } else { + x = _activeCharsXpos[_updateCharNum] + 10; + y = 145; + } + } + + int f = rollDice(1, 6) - 1; + if (f == _characters[_updateCharNum].curFaceFrame) + f++; + if (f > 5) + f -= 5; + f += 7; + + if (speechEnabled()) { + if (snd_updateCharacterSpeech() == 2) + // WORKAROUND for portrait speech animations which would "freeze" in some situations + if (_resetPortraitAfterSpeechAnim == 2) + _resetPortraitAfterSpeechAnim = 1; + else + _updatePortraitSpeechAnimDuration = 2; + else + _updatePortraitSpeechAnimDuration = 1; + } else if (_resetPortraitAfterSpeechAnim == 2) { + _resetPortraitAfterSpeechAnim = 1; + } + + _updatePortraitSpeechAnimDuration--; + + if (_updatePortraitSpeechAnimDuration) { + setCharFaceFrame(_updateCharNum, f); + if (redraw) + gui_drawCharPortraitWithStats(_updateCharNum); + else + gui_drawCharFaceShape(_updateCharNum, x, y, 0); + _updatePortraitNext = _system->getMillis() + 10 * _tickLength; + } else if (_resetPortraitAfterSpeechAnim != 0) { + faceFrameRefresh(_updateCharNum); + if (redraw) { + gui_drawCharPortraitWithStats(_updateCharNum); + initTextFading(0, 0); + } else { + gui_drawCharFaceShape(_updateCharNum, x, y, 0); + } + _updateCharNum = -1; + } +} + +void LoLEngine::stopPortraitSpeechAnim() { + if (_updateCharNum == -1) + return; + + _updatePortraitSpeechAnimDuration = 1; + // WORKAROUND for portrait speech animations which would "freeze" in some situations + _resetPortraitAfterSpeechAnim = 2; + updatePortraitSpeechAnim(); + _updatePortraitSpeechAnimDuration = 1; + _updateCharNum = -1; + + if (!_portraitSpeechAnimMode) + initTextFading(0, 0); +} + +void LoLEngine::initTextFading(int textType, int clearField) { + if (_textColorFlag == textType || !textType) { + _fadeText = true; + _palUpdateTimer = _system->getMillis(); + } + + if (!clearField) + return; + + stopPortraitSpeechAnim(); + if (_needSceneRestore) + _screen->setScreenDim(_txt->clearDim(3)); + + _fadeText = false; + _timer->disable(11); +} + +void LoLEngine::setCharFaceFrame(int charNum, int frameNum) { + _characters[charNum].curFaceFrame = frameNum; +} + +void LoLEngine::faceFrameRefresh(int charNum) { + if (_characters[charNum].curFaceFrame == 1) + setTemporaryFaceFrame(charNum, 0, 0, 0); + else if (_characters[charNum].curFaceFrame == 6) + if (_characters[charNum].tempFaceFrame != 5) + setTemporaryFaceFrame(charNum, 0, 0, 0); + else + _characters[charNum].curFaceFrame = 5; + else + _characters[charNum].curFaceFrame = 0; +} + +void LoLEngine::recalcCharacterStats(int charNum) { + for (int i = 0; i < 5; i++) + _charStatsTemp[i] = calculateCharacterStats(charNum, i); +} + +int LoLEngine::calculateCharacterStats(int charNum, int index) { + if (index == 0) { + // Might + int c = 0; + for (int i = 0; i < 8; i++) + c += _characters[charNum].itemsMight[i]; + if (c) + c += _characters[charNum].might; + else + c = _characters[charNum].defaultModifiers[8]; + + c = (c * _characters[charNum].defaultModifiers[1]) >> 8; + c = (c * _characters[charNum].totalMightModifier) >> 8; + + return c; + + } else if (index == 1) { + // Protection + return calculateProtection(charNum); + + } else if (index > 4) { + return -1; + + } else { + // Fighter + // Rogue + // Mage + index -= 2; + return _characters[charNum].skillLevels[index] + _characters[charNum].skillModifiers[index]; + } + + //return 1; +} + +int LoLEngine::calculateProtection(int index) { + int c = 0; + if (index & 0x8000) { + // Monster + index &= 0x7FFF; + c = (_monsters[index].properties->itemProtection * _monsters[index].properties->fightingStats[2]) >> 8; + } else { + // Character + c = _characters[index].itemProtection + _characters[index].protection; + c = (c * _characters[index].defaultModifiers[2]) >> 8; + c = (c * _characters[index].totalProtectionModifier) >> 8; + } + + return c; +} + +void LoLEngine::setCharacterMagicOrHitPoints(int charNum, int type, int points, int mode) { + static const uint16 barData[4][5] = { + // xPos, bar color, text color, flag, string id + { 0x27, 0x9A, 0x98, 0x01, 0x4254 }, + { 0x21, 0xA2, 0xA0, 0x00, 0x4253 }, + // 16 color mode + { 0x27, 0x66, 0x55, 0x01, 0x4254 }, + { 0x21, 0xAA, 0x99, 0x00, 0x4253 } + }; + + if (charNum > 2) + return; + + LoLCharacter *c = &_characters[charNum]; + if (!(c->flags & 1)) + return; + + int pointsMax = type ? c->magicPointsMax : c->hitPointsMax; + int pointsCur = type ? c->magicPointsCur : c->hitPointsCur; + + int newVal = (mode == 2) ? (pointsMax + points) : (mode ? (pointsCur + points) : points); + newVal = CLIP(newVal, 0, pointsMax); + + if (type) { + c->magicPointsCur = newVal; + } else { + c->hitPointsCur = newVal; + if (c->hitPointsCur < 1) + c->flags |= 8; + } + + if (_updateFlags & 2) + return; + + Screen::FontId cf = _screen->setFont(Screen::FID_6_FNT); + int cp = _screen->setCurPage(0); + + int s = 8192 / pointsMax; + pointsMax = (s * pointsMax) >> 8; + pointsCur = (s * pointsCur) >> 8; + newVal = (s * newVal) >> 8; + int newValScl = CLIP(newVal, 0, pointsMax); + + int step = (newVal > pointsCur) ? 2 : -2; + newVal = CLIP(newVal + step, 0, pointsMax); + + if (_flags.use16ColorMode) + type += 2; + + if (newVal != pointsCur) { + step = (newVal >= pointsCur) ? 2 : -2; + + for (int i = pointsCur; i != newVal || newVal != newValScl;) { + if (ABS(i - newVal) < ABS(step)) + step >>= 1; + + i += step; + + uint32 delayTimer = _system->getMillis() + _tickLength; + + gui_drawLiveMagicBar(barData[type][0] + _activeCharsXpos[charNum], 175, i, 0, pointsMax, 5, 32, barData[type][1], _flags.use16ColorMode ? 0x44 : 1, barData[type][3]); + _screen->printText(getLangString(barData[type][4]), barData[type][0] + _activeCharsXpos[charNum], 144, barData[type][2], 0); + _screen->updateScreen(); + + if (i == newVal) { + newVal = newValScl; + step = -step; + } + + delayUntil(delayTimer); + } + } + + _screen->setFont(cf); + _screen->setCurPage(cp); +} + +void LoLEngine::increaseExperience(int charNum, int skill, uint32 points) { + if (charNum & 0x8000) + return; + + if (_characters[charNum].flags & 8) + return; + + _characters[charNum].experiencePts[skill] += points; + + bool loop = true; + while (loop) { + if (_characters[charNum].experiencePts[skill] < _expRequirements[_characters[charNum].skillLevels[skill]]) + break; + + _characters[charNum].skillLevels[skill]++; + _characters[charNum].flags |= (0x200 << skill); + int inc = 0; + + switch (skill) { + case 0: + _txt->printMessage(0x8003, getLangString(0x4023), _characters[charNum].name); + inc = rollDice(4, 6); + _characters[charNum].hitPointsCur += inc; + _characters[charNum].hitPointsMax += inc; + break; + + case 1: + _txt->printMessage(0x8003, getLangString(0x4025), _characters[charNum].name); + inc = rollDice(2, 6); + _characters[charNum].hitPointsCur += inc; + _characters[charNum].hitPointsMax += inc; + break; + + case 2: + _txt->printMessage(0x8003, getLangString(0x4024), _characters[charNum].name); + inc = (_characters[charNum].defaultModifiers[6] * (rollDice(1, 8) + 17)) >> 8; + _characters[charNum].magicPointsCur += inc; + _characters[charNum].magicPointsMax += inc; + inc = rollDice(1, 6); + _characters[charNum].hitPointsCur += inc; + _characters[charNum].hitPointsMax += inc; + break; + + default: + break; + } + + snd_playSoundEffect(118, -1); + gui_drawCharPortraitWithStats(charNum); + } +} + +void LoLEngine::increaseCharacterHitpoints(int charNum, int points, bool ignoreDeath) { + if (_characters[charNum].hitPointsCur <= 0 && !ignoreDeath) + return; + + if (points <= 1) + points = 1; + + _characters[charNum].hitPointsCur = CLIP(_characters[charNum].hitPointsCur + points, 1, _characters[charNum].hitPointsMax); + _characters[charNum].flags &= 0xFFF7; +} + +void LoLEngine::setupScreenDims() { + if (textEnabled()) { + _screen->modifyScreenDim(4, 11, 124, 28, 45); + _screen->modifyScreenDim(5, 85, 123, 233, 54); + } else { + _screen->modifyScreenDim(4, 11, 124, 28, 9); + _screen->modifyScreenDim(5, 85, 123, 233, 18); + } +} + +void LoLEngine::initSceneWindowDialogue(int controlMode) { + resetPortraitsAndDisableSysTimer(); + gui_prepareForSequence(112, 0, 176, 120, controlMode); + + _updateFlags |= 3; + + _txt->setupField(true); + _txt->expandField(); + setupScreenDims(); + gui_disableControls(controlMode); +} + +void LoLEngine::toggleSelectedCharacterFrame(bool mode) { + if (countActiveCharacters() == 1) + return; + + int col = mode ? 212 : 1; + + int cp = _screen->setCurPage(0); + int x = _activeCharsXpos[_selectedCharacter]; + + _screen->drawBox(x, 143, x + 65, 176, col); + _screen->setCurPage(cp); +} + +void LoLEngine::gui_prepareForSequence(int x, int y, int w, int h, int buttonFlags) { + setSequenceButtons(x, y, w, h, buttonFlags); + + _seqWindowX1 = x; + _seqWindowY1 = y; + _seqWindowX2 = x + w; + _seqWindowY2 = y + h; + + int mouseOffs = _itemInHand ? 10 : 0; + _screen->setMouseCursor(mouseOffs, mouseOffs, getItemIconShapePtr(_itemInHand)); + + _lastMouseRegion = -1; + + if (w == 320) { + setLampMode(false); + _lampStatusSuspended = true; + } +} + +void LoLEngine::gui_specialSceneSuspendControls(int controlMode) { + if (controlMode) { + _updateFlags |= 4; + setLampMode(false); + } + _updateFlags |= 1; + _specialSceneFlag = 1; + _currentControlMode = controlMode; + calcCharPortraitXpos(); + checkFloatingPointerRegions(); +} + +void LoLEngine::gui_specialSceneRestoreControls(int restoreLamp) { + if (restoreLamp) { + _updateFlags &= 0xFFFA; + resetLampStatus(); + } + _updateFlags &= 0xFFFE; + _specialSceneFlag = 0; + checkFloatingPointerRegions(); +} + +void LoLEngine::restoreAfterSceneWindowDialogue(int redraw) { + gui_enableControls(); + _txt->setupField(false); + _updateFlags &= 0xFFDF; + + setDefaultButtonState(); + + for (int i = 0; i < 6; i++) + _tim->freeAnimStruct(i); + + _updateFlags = 0; + + if (redraw) { + if (_screen->_fadeFlag != 2) + _screen->fadeClearSceneWindow(10); + gui_drawPlayField(); + setPaletteBrightness(_screen->getPalette(0), _brightness, _lampEffect); + _screen->_fadeFlag = 0; + } + + _needSceneRestore = 0; + enableSysTimer(2); +} + +void LoLEngine::initDialogueSequence(int controlMode, int pageNum) { + if (controlMode) { + _timer->disable(11); + _fadeText = false; + int cp = _screen->setCurPage(pageNum); + + if (_flags.use16ColorMode) { + _screen->fillRect(0, 128, 319, 199, 0x44); + gui_drawBox(0, 129, 320, 71, 0xEE, 0xCC, -1); + gui_drawBox(1, 130, 318, 69, 0xEE, 0xCC, 0x11); + } else { + _screen->fillRect(0, 128, 319, 199, 1); + gui_drawBox(0, 129, 320, 71, 136, 251, -1); + gui_drawBox(1, 130, 318, 69, 136, 251, 252); + } + + _screen->modifyScreenDim(5, 8, 131, 306, 66); + _screen->modifyScreenDim(4, 1, 133, 38, 60); + _txt->clearDim(4); + + _updateFlags |= 2; + _currentControlMode = controlMode; + calcCharPortraitXpos(); + + if (!textEnabled() && (!(controlMode & 2))) { + int nc = countActiveCharacters(); + for (int i = 0; i < nc; i++) { + _portraitSpeechAnimMode = 2; + _updateCharNum = i; + _screen->drawShape(0, _gameShapes[88], _activeCharsXpos[_updateCharNum] + 8, 142, 0, 0); + stopPortraitSpeechAnim(); + } + } + + _screen->setCurPage(cp); + + } else { + _txt->setupField(true); + _txt->expandField(); + setupScreenDims(); + _txt->clearDim(4); + } + + _currentControlMode = controlMode; + _dialogueField = true; +} + +void LoLEngine::restoreAfterDialogueSequence(int controlMode) { + if (!_dialogueField) + return; + + stopPortraitSpeechAnim(); + _currentControlMode = controlMode; + calcCharPortraitXpos(); + + if (_currentControlMode) { + _screen->modifyScreenDim(4, 11, 124, 28, 45); + _screen->modifyScreenDim(5, 85, 123, 233, 54); + _updateFlags &= 0xFFFD; + } else { + const ScreenDim *d = _screen->getScreenDim(5); + _screen->fillRect(d->sx, d->sy, d->sx + d->w - (_flags.use16ColorMode ? 3 : 2), d->sy + d->h - 2, d->unkA); + _txt->clearDim(4); + _txt->setupField(false); + } + + _dialogueField = false; +} + +void LoLEngine::resetPortraitsAndDisableSysTimer() { + _needSceneRestore = 1; + if (!textEnabled() || (!(_currentControlMode & 2))) + timerUpdatePortraitAnimations(1); + + disableSysTimer(2); +} + +void LoLEngine::fadeText() { + if (!_fadeText) + return; + + if (_screen->fadeColor(192, 252, (_system->getMillis() - _palUpdateTimer) / _tickLength, 60)) + return; + + if (_needSceneRestore) + return; + + _screen->setScreenDim(_txt->clearDim(3)); + + _timer->disable(11); + + _fadeText = false; +} + +void LoLEngine::setPaletteBrightness(const Palette &srcPal, int brightness, int modifier) { + generateBrightnessPalette(srcPal, _screen->getPalette(1), brightness, modifier); + _screen->fadePalette(_screen->getPalette(1), 5, 0); + _screen->_fadeFlag = 0; +} + +void LoLEngine::generateBrightnessPalette(const Palette &src, Palette &dst, int brightness, int16 modifier) { + dst.copy(src); + if (_flags.use16ColorMode) { + if (!brightness) + modifier = 0; + else if (modifier < 0 || modifier > 7 || !(_flagsTable[31] & 0x08)) + modifier = 8; + + modifier >>= 1; + if (modifier) + modifier--; + if (modifier > 3) + modifier = 3; + _blockBrightness = modifier << 4; + _sceneUpdateRequired = true; + + } else { + _screen->loadSpecialColors(dst); + + brightness = (8 - brightness) << 5; + if (modifier >= 0 && modifier < 8 && (_flagsTable[31] & 0x08)) { + brightness = 256 - ((((modifier & 0xFFFE) << 5) * (256 - brightness)) >> 8); + if (brightness < 0) + brightness = 0; + } + + for (int i = 0; i < 384; i++) { + uint16 c = (dst[i] * brightness) >> 8; + dst[i] = c & 0xFF; + } + } +} + +void LoLEngine::generateFlashPalette(const Palette &src, Palette &dst, int colorFlags) { + dst.copy(src, 0, 2); + + for (int i = 2; i < 128; i++) { + for (int ii = 0; ii < 3; ii++) { + uint8 t = src[i * 3 + ii] & 0x3F; + if (colorFlags & (1 << ii)) + t += ((0x3F - t) >> 1); + else + t -= (t >> 1); + dst[i * 3 + ii] = t; + } + } + + dst.copy(src, 128); +} + +void LoLEngine::createTransparencyTables() { + if (_flags.isTalkie || _loadSuppFilesFlag) + return; + + uint8 *tpal = new uint8[768]; + + if (_flags.use16ColorMode) { + static const uint8 colTbl[] = { + 0x00, 0x00, 0x11, 0x00, 0x22, 0x00, 0x33, 0x00, 0x44, 0x00, 0x55, 0x00, 0x66, 0x00, 0x77, 0x00, + 0x88, 0x00, 0x99, 0x00, 0xAA, 0x00, 0xBB, 0x00, 0xCC, 0x00, 0xDD, 0x00, 0xEE, 0x00, 0xFF, 0x00 + }; + + memset(tpal, 0xFF, 768); + _res->loadFileToBuf("LOL.NOL", tpal, 48); + + for (int i = 15; i > -1; i--) { + int s = colTbl[i << 1] * 3; + tpal[s] = tpal[i * 3]; + tpal[s + 1] = tpal[i * 3 + 1]; + tpal[s + 2] = tpal[i * 3 + 2]; + tpal[i * 3 + 2] = tpal[i * 3 + 1] = tpal[i * 3] = 0xFF; + } + + _screen->createTransparencyTablesIntern(colTbl, 16, tpal, tpal, _transparencyTable1, _transparencyTable2, 80); + + } else { + _res->loadFileToBuf("fxpal.col", tpal, 768); + _screen->loadBitmap("fxpal.shp", 3, 3, 0); + const uint8 *shpPal = _screen->getPtrToShape(_screen->getCPagePtr(2), 0) + 11; + + _screen->createTransparencyTablesIntern(shpPal, 20, tpal, _screen->getPalette(1).getData(), _transparencyTable1, _transparencyTable2, 70); + } + + delete[] tpal; + _loadSuppFilesFlag = 1; +} + +void LoLEngine::updateSequenceBackgroundAnimations() { + if (_updateFlags & 8 || !_tim) + return; + if (!_tim->animator()) + return; + + for (int i = 0; i < 6; i++) + _tim->animator()->update(i); +} + +void LoLEngine::loadTalkFile(int index) { + if (index == _curTlkFile) + return; + + if (_curTlkFile > 0 && index > 0) + _res->unloadPakFile(Common::String::format("%02d.TLK", _curTlkFile)); + + if (index > 0) + _curTlkFile = index; + + _res->loadPakFile(Common::String::format("%02d.TLK", index)); +} + +int LoLEngine::characterSays(int track, int charId, bool redraw) { + if (charId == 1) { + charId = _selectedCharacter; + } if (charId <= 0) { + charId = 0; + } else { + int i = 0; + for (; i < 4; i++) { + if (charId != _characters[i].id || !(_characters[i].flags & 1)) + continue; + charId = i; + break; + } + + if (i == 4) + return 0; + } + + bool r = snd_playCharacterSpeech(track, charId, 0); + + if (r && redraw) { + stopPortraitSpeechAnim(); + _updateCharNum = charId; + _portraitSpeechAnimMode = 0; + _resetPortraitAfterSpeechAnim = 1; + _fadeText = false; + updatePortraitSpeechAnim(); + } + + return r ? (textEnabled() ? 1 : 0) : 1; +} + +int LoLEngine::playCharacterScriptChat(int charId, int mode, int restorePortrait, char *str, EMCState *script, const uint16 *paramList, int16 paramIndex) { + int ch = 0; + bool skipAnim = false; + + if ((charId == -1) || (!(charId & 0x70))) + charId = ch = (charId == 1) ? (_selectedCharacter ? _characters[_selectedCharacter].id : 0) : charId; + else + charId ^= 0x70; + + stopPortraitSpeechAnim(); + + if (charId < 0) { + charId = ch = _rnd.getRandomNumber(countActiveCharacters() - 1); + } else if (charId > 0) { + int i = 0; + + for (; i < 3; i++) { + if (_characters[i].id != charId || !(_characters[i].flags & 1)) + continue; + if (charId == ch) + ch = i; + charId = i; + break; + } + + if (i == 4) { + if (charId == 8) + skipAnim = true; + else + return 0; + } + } + + if (!skipAnim) { + _updateCharNum = charId; + _portraitSpeechAnimMode = mode; + _updatePortraitSpeechAnimDuration = strlen(str) >> 1; + _resetPortraitAfterSpeechAnim = restorePortrait; + } + + if (script) + snd_playCharacterSpeech(script->stack[script->sp + 2], ch, 0); + else if (paramList) + snd_playCharacterSpeech(paramList[2], ch, 0); + + if (textEnabled()) { + if (mode == 0) { + _txt->printDialogueText(3, str, script, paramList, paramIndex); + + } else if (mode == 1) { + _txt->clearDim(4); + _screen->modifyScreenDim(4, 16, 123, 23, 47); + _txt->printDialogueText(4, str, script, paramList, paramIndex); + _screen->modifyScreenDim(4, 11, 123, 28, 47); + + } else if (mode == 2) { + _txt->clearDim(4); + _screen->modifyScreenDim(4, 9, 133, 30, 60); + _txt->printDialogueText(4, str, script, paramList, 3); + _screen->modifyScreenDim(4, 1, 133, 37, 60); + } + } + + _fadeText = false; + if (!skipAnim) + updatePortraitSpeechAnim(); + + return 1; +} + +void LoLEngine::setupDialogueButtons(int numStr, const char *s1, const char *s2, const char *s3) { + screen()->setScreenDim(5); + + if (numStr == 1 && speechEnabled()) { + _dialogueNumButtons = 0; + _dialogueButtonString[0] = _dialogueButtonString[1] = _dialogueButtonString[2] = 0; + } else { + _dialogueNumButtons = numStr; + _dialogueButtonString[0] = s1; + _dialogueButtonString[1] = s2; + _dialogueButtonString[2] = s3; + _dialogueHighlightedButton = 0; + + const ScreenDim *d = screen()->getScreenDim(5); + + static uint16 posX[3]; + static uint8 posY[3]; + + memset(posY, d->sy + d->h - 9, 3); + + _dialogueButtonPosX = posX; + _dialogueButtonPosY = posY; + + if (numStr == 1) { + posX[0] = posX[1] = posX[2] = d->sx + d->w - (_dialogueButtonWidth + 3); + } else { + int xOffs = d->w / numStr; + posX[0] = d->sx + (xOffs >> 1) - 37; + posX[1] = posX[0] + xOffs; + posX[2] = posX[1] + xOffs; + } + + drawDialogueButtons(); + } + + if (!shouldQuit()) + removeInputTop(); +} + +void LoLEngine::giveItemToMonster(LoLMonster *monster, Item item) { + uint16 *c = &monster->assignedItems; + while (*c) + c = &_itemsInPlay[*c].nextAssignedObject; + *c = (uint16)item; + _itemsInPlay[item].nextAssignedObject = 0; +} + +const uint16 *LoLEngine::getCharacterOrMonsterStats(int id) { + return (id & 0x8000) ? (const uint16 *)_monsters[id & 0x7FFF].properties->fightingStats : _characters[id].defaultModifiers; +} + +uint16 *LoLEngine::getCharacterOrMonsterItemsMight(int id) { + return (id & 0x8000) ? _monsters[id & 0x7FFF].properties->itemsMight : _characters[id].itemsMight; +} + +uint16 *LoLEngine::getCharacterOrMonsterProtectionAgainstItems(int id) { + return (id & 0x8000) ? _monsters[id & 0x7FFF].properties->protectionAgainstItems : _characters[id].protectionAgainstItems; +} + +void LoLEngine::delay(uint32 millis, bool doUpdate, bool) { + while (millis && !shouldQuit()) { + if (doUpdate) + update(); + else + updateInput(); + + uint32 step = MIN(millis, _tickLength); + _system->delayMillis(step); + millis -= step; + } +} + +const KyraRpgGUISettings *LoLEngine::guiSettings() { + return &_guiSettings; +} + +// spells + +int LoLEngine::castSpell(int charNum, int spellType, int spellLevel) { + _activeSpell.charNum = charNum; + _activeSpell.spell = spellType; + _activeSpell.p = &_spellProperties[spellType]; + + _activeSpell.level = ABS(spellLevel); + + if ((_spellProperties[spellType].flags & 0x100) && testWallFlag(calcNewBlockPosition(_currentBlock, _currentDirection), _currentDirection, 1)) { + _txt->printMessage(2, "%s", getLangString(0x4257)); + return 0; + } + + if (charNum < 0) { + _activeSpell.charNum = (charNum * -1) - 1; + if (_spellProcs[spellType]->isValid()) + return (*_spellProcs[spellType])(&_activeSpell); + } else { + if (_activeSpell.p->mpRequired[spellLevel] > _characters[charNum].magicPointsCur) + return 0; + + if (_activeSpell.p->hpRequired[spellLevel] >= _characters[charNum].hitPointsCur) + return 0; + + setCharacterMagicOrHitPoints(charNum, 1, -_activeSpell.p->mpRequired[spellLevel], 1); + setCharacterMagicOrHitPoints(charNum, 0, -_activeSpell.p->hpRequired[spellLevel], 1); + gui_drawCharPortraitWithStats(charNum); + + if (_spellProcs[spellType]->isValid()) + (*_spellProcs[spellType])(&_activeSpell); + } + + return 1; +} + +int LoLEngine::castSpark(ActiveSpell *a) { + processMagicSpark(a->charNum, a->level); + return 1; +} + +int LoLEngine::castHeal(ActiveSpell *a) { + if (a->level < 3) + processMagicHealSelectTarget(); + else + processMagicHeal(-1, a->level); + + return 1; +} + +int LoLEngine::castIce(ActiveSpell *a) { + processMagicIce(a->charNum, a->level); + return 1; +} + +int LoLEngine::castFireball(ActiveSpell *a) { + processMagicFireball(a->charNum, a->level); + return 1; +} + +int LoLEngine::castHandOfFate(ActiveSpell *a) { + processMagicHandOfFate(a->level); + return 1; +} + +int LoLEngine::castMistOfDoom(ActiveSpell *a) { + processMagicMistOfDoom(a->charNum, a->level); + return 1; +} + +int LoLEngine::castLightning(ActiveSpell *a) { + processMagicLightning(a->charNum, a->level); + return 1; +} + +int LoLEngine::castFog(ActiveSpell *a) { + processMagicFog(); + return 1; +} + +int LoLEngine::castSwarm(ActiveSpell *a) { + processMagicSwarm(a->charNum, 10); + return 1; +} + +int LoLEngine::castVaelansCube(ActiveSpell *a) { + return processMagicVaelansCube(); +} + +int LoLEngine::castGuardian(ActiveSpell *a) { + return processMagicGuardian(a->charNum); +} + +int LoLEngine::castHealOnSingleCharacter(ActiveSpell *a) { + processMagicHeal(a->target, a->level); + return 1; +} + +int LoLEngine::processMagicSpark(int charNum, int spellLevel) { + WSAMovie_v2 *mov = new WSAMovie_v2(this); + _screen->copyPage(0, 12); + + mov->open("spark1.wsa", 0, 0); + if (!mov->opened()) + error("SPARK: Unable to load SPARK1.WSA"); + snd_playSoundEffect(72, -1); + playSpellAnimation(mov, 0, 7, 4, _activeCharsXpos[charNum] - 2, 138, 0, 0, 0, 0, false); + mov->close(); + + _screen->copyPage(12, 0); + _screen->updateScreen(); + + uint16 targetBlock = 0; + int dist = getSpellTargetBlock(_currentBlock, _currentDirection, 4, targetBlock); + uint16 target = getNearestMonsterFromCharacterForBlock(targetBlock, charNum); + + static const uint8 dmg[] = { 7, 15, 25, 60 }; + if (target != 0xFFFF) { + inflictMagicalDamage(target, charNum, dmg[spellLevel], 5, 0); + updateDrawPage2(); + gui_drawScene(0); + _screen->copyPage(0, 12); + } + + int numFrames = mov->open("spark2.wsa", 0, 0); + if (!mov->opened()) + error("SPARK: Unable to load SPARK2.WSA"); + + uint16 wX[6]; + uint16 wY[6]; + uint16 wFrames[6]; + const uint16 width = mov->width(); + const uint16 height = mov->height(); + + for (int i = 0; i < 6; i++) { + wX[i] = (_rnd.getRandomNumber(0x7FFF) % 64) + ((176 - width) >> 1) + 80; + wY[i] = (_rnd.getRandomNumber(0x7FFF) % 32) + ((120 - height) >> 1) - 16; + wFrames[i] = i << 1; + } + + for (int i = 0, d = ((spellLevel << 1) + 12); i < d; i++) { + uint32 delayTimer = _system->getMillis() + 4 * _tickLength; + _screen->copyPage(12, 2); + + for (int ii = 0; ii <= spellLevel; ii++) { + if (wFrames[ii] >= i || wFrames[ii] + 13 <= i) + continue; + + if ((i - wFrames[ii]) == 1) + snd_playSoundEffect(162, -1); + + mov->displayFrame(((i - wFrames[ii]) + (dist << 4)) % numFrames, 2, wX[ii], wY[ii], 0x5000, _transparencyTable1, _transparencyTable2); + _screen->copyRegion(wX[ii], wY[ii], wX[ii], wY[ii], width, height, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + if (i < d - 1) + delayUntil(delayTimer); + } + + mov->close(); + + _screen->copyPage(12, 2); + updateDrawPage2(); + + _sceneUpdateRequired = true; + + delete mov; + return 1; +} + +int LoLEngine::processMagicHealSelectTarget() { + _txt->printMessage(0, "%s", getLangString(0x4040)); + gui_resetButtonList(); + gui_setFaceFramesControlButtons(81, 0); + gui_initButtonsFromList(_buttonList8); + return 1; +} + +int LoLEngine::processMagicHeal(int charNum, int spellLevel) { + if (!_healOverlay) { + _healOverlay = new uint8[256]; + Palette tpal(256); + tpal.copy(_screen->getPalette(1)); + + if (_flags.use16ColorMode) { + tpal.fill(16, 240, 0xFF); + uint8 *dst = tpal.getData(); + for (int i = 1; i < 16; i++) { + int s = ((i << 4) | i) * 3; + SWAP(dst[s], dst[i]); + SWAP(dst[s + 1], dst[i + 1]); + SWAP(dst[s + 2], dst[i + 2]); + } + } + + _screen->generateGrayOverlay(tpal, _healOverlay, 52, 22, 20, 0, 256, true); + } + + const uint8 *healShpFrames = 0; + const uint8 *healiShpFrames = 0; + bool curePoison = false; + int points = 0; + + if (spellLevel == 0) { + points = 25; + healShpFrames = _healShapeFrames; + healiShpFrames = _healShapeFrames + 32; + + } else if (spellLevel == 1) { + points = 45; + healShpFrames = _healShapeFrames + 16; + healiShpFrames = _healShapeFrames + 48; + + } else if (spellLevel > 3) { + curePoison = true; + points = spellLevel; + healShpFrames = _healShapeFrames + 16; + healiShpFrames = _healShapeFrames + 64; + + } else { + curePoison = true; + points = 10000; + healShpFrames = _healShapeFrames + 16; + healiShpFrames = _healShapeFrames + 64; + } + + int ch = 0; + int n = 4; + + if (charNum != -1) { + ch = charNum; + n = charNum + 1; + } + + charNum = ch; + + uint16 pX[4]; + uint16 pY = 138; + uint16 diff[4]; + uint16 pts[4]; + memset(pts, 0, sizeof(pts)); + + while (charNum < n) { + if (!(_characters[charNum].flags & 1)) { + charNum++; + continue; + } + + pX[charNum] = _activeCharsXpos[charNum] - 6; + _characters[charNum].damageSuffered = 0; + int dmg = _characters[charNum].hitPointsMax - _characters[charNum].hitPointsCur; + diff[charNum] = (dmg < points) ? dmg : points; + _screen->copyRegion(pX[charNum], pY, charNum * 77, 32, 77, 44, 0, 2, Screen::CR_NO_P_CHECK); + charNum++; + } + + int cp = _screen->setCurPage(2); + snd_playSoundEffect(68, -1); + + for (int i = 0; i < 16; i++) { + uint32 delayTimer = _system->getMillis() + 4 * _tickLength; + + for (charNum = ch; charNum < n; charNum++) { + if (!(_characters[charNum].flags & 1)) + continue; + + _screen->copyRegion(charNum * 77, 32, pX[charNum], pY, 77, 44, 2, 2, Screen::CR_NO_P_CHECK); + + pts[charNum] &= 0xFF; + pts[charNum] += ((diff[charNum] << 8) / 16); + increaseCharacterHitpoints(charNum, pts[charNum] / 256, true); + gui_drawCharPortraitWithStats(charNum); + + _screen->drawShape(2, _healShapes[healShpFrames[i]], pX[charNum], pY, 0, 0x1000, _transparencyTable1, _transparencyTable2); + _screen->fillRect(0, 0, 31, 31, 0); + + _screen->drawShape(_screen->_curPage, _healiShapes[healiShpFrames[i]], 0, 0, 0, 0); + _screen->applyOverlaySpecial(_screen->_curPage, 0, 0, 2, pX[charNum] + 7, pY + 6, 32, 32, 0, 0, _healOverlay); + + _screen->copyRegion(pX[charNum], pY, pX[charNum], pY, 77, 44, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + delayUntil(delayTimer); + } + + for (charNum = ch; charNum < n; charNum++) { + if (!(_characters[charNum].flags & 1)) + continue; + + _screen->copyRegion(charNum * 77, 32, pX[charNum], pY, 77, 44, 2, 2, Screen::CR_NO_P_CHECK); + + if (curePoison) + removeCharacterEffects(&_characters[charNum], 4, 4); + + gui_drawCharPortraitWithStats(charNum); + _screen->copyRegion(pX[charNum], pY, pX[charNum], pY, 77, 44, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + _screen->setCurPage(cp); + updateDrawPage2(); + return 1; +} + +int LoLEngine::processMagicIce(int charNum, int spellLevel) { + int cp = _screen->setCurPage(2); + + disableSysTimer(2); + + gui_drawScene(0); + _screen->copyPage(0, 12); + + Palette tpal(256), swampCol(256); + + if (_currentLevel == 11 && !(_flagsTable[52] & 0x04)) { + uint8 *sc = _screen->getPalette(0).getData(); + uint8 *dc = _screen->getPalette(2).getData(); + for (int i = 1; i < (_screen->getPalette(0).getNumColors() * 3); i++) + SWAP(sc[i], dc[i]); + + _flagsTable[52] |= 0x04; + static const uint8 freezeTimes[] = { 20, 28, 40, 60 }; + setCharacterUpdateEvent(charNum, 8, freezeTimes[spellLevel], 1); + } + + Palette s(256); + s.copy(_screen->getPalette(1)); + if (_flags.use16ColorMode) { + _screen->loadPalette("LOLICE.NOL", swampCol); + for (int i = 1; i < 16; i++) { + uint16 v = (s[i * 3] + s[i * 3 + 1] + s[i * 3 + 2]) / 3; + tpal[i * 3] = 0; + tpal[i * 3 + 1] = v; + tpal[i * 3 + 2] = v << 1; + + if (tpal[i * 3 + 2] > 29) + tpal[i * 3 + 2] = 29; + } + + } else { + _screen->loadPalette("SWAMPICE.COL", swampCol); + tpal.copy(s, 128); + swampCol.copy(s, 128); + + for (int i = 1; i < 128; i++) { + tpal[i * 3] = 0; + uint16 v = (s[i * 3] + s[i * 3 + 1] + s[i * 3 + 2]) / 3; + tpal[i * 3 + 1] = v; + tpal[i * 3 + 2] = v << 1; + + if (tpal[i * 3 + 2] > 0x3F) + tpal[i * 3 + 2] = 0x3F; + } + } + + generateBrightnessPalette(tpal, tpal, _brightness, _lampEffect); + generateBrightnessPalette(swampCol, swampCol, _brightness, _lampEffect); + swampCol[0] = swampCol[1] = swampCol[2] = tpal[0] = tpal[1] = tpal[2] = 0; + + generateBrightnessPalette(_screen->getPalette(0), s, _brightness, _lampEffect); + + int sX = 112; + int sY = 0; + WSAMovie_v2 *mov = new WSAMovie_v2(this); + + if (spellLevel == 0) { + sX = 0; + } if (spellLevel == 1 || spellLevel == 2) { + mov->open("SNOW.WSA", 1, 0); + if (!mov->opened()) + error("Ice: Unable to load snow.wsa"); + } if (spellLevel == 3) { + mov->open("ICE.WSA", 1, 0); + if (!mov->opened()) + error("Ice: Unable to load ice.wsa"); + sX = 136; + sY = 12; + } + + snd_playSoundEffect(71, -1); + + playSpellAnimation(0, 0, 0, 2, 0, 0, 0, s.getData(), tpal.getData(), 40, false); + + _screen->timedPaletteFadeStep(s.getData(), tpal.getData(), _system->getMillis(), _tickLength); + if (mov->opened()) { + int r = true; + if (spellLevel > 2) { + _levelBlockProperties[calcNewBlockPosition(_currentBlock, _currentDirection)].flags |= 0x10; + snd_playSoundEffect(165, -1); + r = false; + }; + + playSpellAnimation(mov, 0, mov->frames(), 2, sX, sY, 0, 0, 0, 0, r); + mov->close(); + } + + delete mov; + static const uint8 snowDamage[] = { 10, 20, 30, 55 }; + static const uint8 iceDamageMax[] = {1, 2, 15, 20, 35}; + static const uint8 iceDamageMin[] = {10, 10, 3, 4, 4}; + static const uint8 iceDamageAdd[] = {5, 10, 30, 10, 10}; + + bool breakWall = false; + + if (spellLevel < 3) { + inflictMagicalDamageForBlock(calcNewBlockPosition(_currentBlock, _currentDirection), charNum, snowDamage[spellLevel], 3); + } else { + uint16 o = _levelBlockProperties[calcNewBlockPosition(_currentBlock, _currentDirection)].assignedObjects; + while (o & 0x8000) { + int might = rollDice(iceDamageMin[spellLevel], iceDamageMax[spellLevel]) + iceDamageAdd[spellLevel]; + int dmg = calcInflictableDamagePerItem(charNum, 0, might, 3, 2); + + LoLMonster *m = &_monsters[o & 0x7FFF]; + if (m->hitPoints <= dmg) { + increaseExperience(charNum, 2, m->hitPoints); + o = m->nextAssignedObject; + + if (m->flags & 0x20) { + m->mode = 0; + monsterDropItems(m); + if (_currentLevel != 29) + setMonsterMode(m, 14); + runLevelScriptCustom(0x404, -1, o, o, 0, 0); + checkSceneUpdateNeed(m->block); + if (m->mode != 14) + placeMonster(m, 0, 0); + + } else { + killMonster(m); + } + + } else { + breakWall = true; + inflictDamage(o, dmg, charNum, 2, 3); + m->damageReceived = 0; + o = m->nextAssignedObject; + } + + if (m->flags & 0x20) + break; + } + } + + updateDrawPage2(); + gui_drawScene(0); + enableSysTimer(2); + + if (_currentLevel != 11) + generateBrightnessPalette(_screen->getPalette(0), swampCol, _brightness, _lampEffect); + + playSpellAnimation(0, 0, 0, 2, 0, 0, 0, tpal.getData(), swampCol.getData(), 40, 0); + + _screen->timedPaletteFadeStep(tpal.getData(), swampCol.getData(), _system->getMillis(), _tickLength); + + if (breakWall) + breakIceWall(tpal.getData(), swampCol.getData()); + + _screen->setCurPage(cp); + return 1; +} + +int LoLEngine::processMagicFireball(int charNum, int spellLevel) { + int fbCnt = 0; + int d = 1; + + if (spellLevel == 0) { + fbCnt = 4; + } else if (spellLevel == 1) { + fbCnt = 5; + } else if (spellLevel == 2) { + fbCnt = 6; + } else if (spellLevel == 3) { + d = 0; + fbCnt = 5; + } + + int drawPage1 = 2; + int drawPage2 = 4; + + int bl = _currentBlock; + int fireballItem = makeItem(9, 0, 0); + + int i = 0; + for (; i < 3; i++) { + runLevelScriptCustom(bl, 0x200, -1, fireballItem, 0, 0); + uint16 o = _levelBlockProperties[bl].assignedObjects; + + if ((o & 0x8000) || (_wllWallFlags[_levelBlockProperties[bl].walls[_currentDirection ^ 2]] & 7)) { + while (o & 0x8000) { + static const uint8 fireballDamage[] = { 20, 40, 80, 100 }; + int dmg = calcInflictableDamagePerItem(charNum, o, fireballDamage[spellLevel], 4, 1); + LoLMonster *m = &_monsters[o & 0x7FFF]; + o = m->nextAssignedObject; + _envSfxUseQueue = true; + inflictDamage(m->id | 0x8000, dmg, charNum, 2, 4); + _envSfxUseQueue = false; + } + break; + } + + bl = calcNewBlockPosition(bl, _currentDirection); + } + + d += i; + if (d > 3) + d = 3; + + deleteItem(fireballItem); + + snd_playSoundEffect(69, -1); + + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 12); + + int fireBallWH = (d << 4) * -1; + int numFireballs = 1; + if (fbCnt > 3) + numFireballs = fbCnt - 3; + + FireballState *fireballState[3]; + memset(&fireballState, 0, sizeof(fireballState)); + for (i = 0; i < numFireballs; i++) + fireballState[i] = new FireballState(i); + + _screen->copyPage(12, drawPage1); + + for (i = 0; i < numFireballs;) { + _screen->setCurPage(drawPage1); + uint32 ctime = _system->getMillis(); + + for (int ii = 0; ii < MIN(fbCnt, 3); ii++) { + FireballState *fb = fireballState[ii]; + if (!fb) + continue; + if (!fb->active) + continue; + + static const int8 finShpIndex1[] = { 5, 6, 7, 7, 6, 5 }; + static const int8 finShpIndex2[] = { -1, 1, 2, 3, 4, -1 }; + uint8 *shp = fb->finalize ? _fireballShapes[finShpIndex1[fb->finProgress]] : _fireballShapes[0]; + + int fX = (((fb->progress * _fireBallCoords[fb->tblIndex & 0xFF]) >> 16) + fb->destX) - ((fb->progress / 8 + shp[3] + fireBallWH) >> 1); + int fY = (((fb->progress * _fireBallCoords[(fb->tblIndex + 64) & 0xFF]) >> 16) + fb->destY) - ((fb->progress / 8 + shp[2] + fireBallWH) >> 1); + int sW = ((fb->progress / 8 + shp[3] + fireBallWH) << 8) / shp[3]; + int sH = ((fb->progress / 8 + shp[2] + fireBallWH) << 8) / shp[2]; + + if (fb->finalize) { + if (_flags.use16ColorMode) + _screen->drawShape(_screen->_curPage, shp, fX, fY, 0, 4, sW, sH); + else + _screen->drawShape(_screen->_curPage, shp, fX, fY, 0, 0x1004, _transparencyTable1, _transparencyTable2, sW, sH); + + if (finShpIndex2[fb->finProgress] != -1) { + shp = _fireballShapes[finShpIndex2[fb->finProgress]]; + fX = (((fb->progress * _fireBallCoords[fb->tblIndex & 0xFF]) >> 16) + fb->destX) - ((fb->progress / 8 + shp[3] + fireBallWH) >> 1); + fY = (((fb->progress * _fireBallCoords[(fb->tblIndex + 64) & 0xFF]) >> 16) + fb->destY) - ((fb->progress / 8 + shp[2] + fireBallWH) >> 1); + sW = ((fb->progress / 8 + shp[3] + fireBallWH) << 8) / shp[3]; + sH = ((fb->progress / 8 + shp[2] + fireBallWH) << 8) / shp[2]; + _screen->drawShape(_screen->_curPage, shp, fX, fY, 0, 4, sW, sH); + } + + } else { + if (_flags.use16ColorMode) + _screen->drawShape(_screen->_curPage, shp, fX, fY, 0, 4, sW, sH); + else + _screen->drawShape(_screen->_curPage, shp, fX, fY, 0, 0x1004, _transparencyTable1, _transparencyTable2, sW, sH); + } + + if (fb->finalize) { + if (++fb->finProgress >= 6) { + fb->active = false; + i++; + } + } else { + if (fb->step < 40) + fb->step += 2; + else + fb->step = 40; + + if (fb->progress < fb->step) { + if (ii < 1) { + fb->progress = fb->step = fb->finProgress = 0; + fb->finalize = true; + } else { + fb->active = false; + i++; + } + + static const uint8 fireballSfx[] = { 98, 167, 167, 168 }; + snd_playSoundEffect(fireballSfx[d], -1); + + } else { + fb->progress -= fb->step; + } + } + } + + int del = _tickLength - (_system->getMillis() - ctime); + if (del > 0) + delay(del); + + _screen->checkedPageUpdate(drawPage1, drawPage2); + _screen->updateScreen(); + SWAP(drawPage1, drawPage2); + _screen->copyPage(12, drawPage1); + } + + for (i = 0; i < numFireballs; i++) + delete fireballState[i]; + + _screen->setCurPage(cp); + _screen->copyPage(12, 0); + _screen->updateScreen(); + updateDrawPage2(); + snd_playQueuedEffects(); + runLevelScriptCustom(bl, 0x20, charNum, 3, 0, 0); + return 1; +} + +int LoLEngine::processMagicHandOfFate(int spellLevel) { + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 12); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + mov->open("hand.wsa", 1, 0); + if (!mov->opened()) + error("Hand: Unable to load HAND.WSA"); + + static const uint8 frames[] = { 17, 26, 11, 16, 27, 35, 27, 35, 0, 75 }; + + snd_playSoundEffect(173, -1); + playSpellAnimation(mov, 0, 10, 3, 112, 0, 0, 0, 0, 0, false); + snd_playSoundEffect(151, -1); + playSpellAnimation(mov, frames[spellLevel * 2] , frames[spellLevel * 2 + 1], 3, 112, 0, 0, 0, 0, 0, false); + snd_playSoundEffect(18, -1); + playSpellAnimation(mov, 10, 0, 3, 112, 0, 0, 0, 0, 0, false); + + mov->close(); + delete mov; + + _screen->setCurPage(cp); + _screen->copyPage(12, 2); + gui_drawScene(2); + + if (spellLevel < 2) { + uint16 b1 = calcNewBlockPosition(_currentBlock, _currentDirection); + uint16 b2 = calcNewBlockPosition(b1, _currentDirection); + + if (!testWallFlag(b2, 0, 4)) { + if (!(_levelBlockProperties[b2].assignedObjects & 0x8000)) { + checkSceneUpdateNeed(b1); + + uint16 dir = (_currentDirection << 1); + uint16 o = _levelBlockProperties[b1].assignedObjects; + while (o & 0x8000) { + uint16 o2 = o; + LoLMonster *m = &_monsters[o & 0x7FFF]; + o = findObject(o)->nextAssignedObject; + int nX = 0; + int nY = 0; + + getNextStepCoords(m->x, m->y, nX, nY, dir); + for (int i = 0; i < 7; i++) + getNextStepCoords(nX, nY, nX, nY, dir); + + placeMonster(m, nX, nY); + runLevelScriptCustom(b2, 0x800, -1, o2, 0, 0); + } + } + } + + } else { + uint16 b1 = calcNewBlockPosition(_currentBlock, _currentDirection); + checkSceneUpdateNeed(b1); + + static const uint16 damage[] = { 75, 125, 175 }; + uint16 o = _levelBlockProperties[b1].assignedObjects; + + while (o & 0x8000) { + uint16 t = o; + o = findObject(o)->nextAssignedObject; + // This might be a bug in the original code, but using + // the hand of fate spell won't give any experience points + int dmg = calcInflictableDamagePerItem(-1, t, damage[spellLevel - 2], 0x80, 1); + inflictDamage(t, dmg, 0xFFFF, 3, 0x80); + } + } + + if (_currentLevel == 29) + _screen->copyPage(12, 2); + + _screen->copyPage(2, 0); + _screen->updateScreen(); + + gui_drawScene(2); + updateDrawPage2(); + return 1; +} + +int LoLEngine::processMagicMistOfDoom(int charNum, int spellLevel) { + static const uint8 mistDamage[] = { 30, 70, 110, 200 }; + + _envSfxUseQueue = true; + inflictMagicalDamageForBlock(calcNewBlockPosition(_currentBlock, _currentDirection), charNum, mistDamage[spellLevel], 0x80); + _envSfxUseQueue = false; + + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 2); + gui_drawScene(2); + _screen->copyPage(2, 12); + + snd_playSoundEffect(155, -1); + + Common::String wsafile = Common::String::format("mists%0d.wsa", spellLevel + 1); + WSAMovie_v2 *mov = new WSAMovie_v2(this); + mov->open(wsafile.c_str(), 1, 0); + if (!mov->opened()) + error("Mist: Unable to load %s", wsafile.c_str()); + + snd_playSoundEffect(_mistAnimData[spellLevel].sound, -1); + playSpellAnimation(mov, _mistAnimData[spellLevel].part1First, _mistAnimData[spellLevel].part1Last, 7, 112, 0, 0, 0, 0, 0, false); + playSpellAnimation(mov, _mistAnimData[spellLevel].part2First, _mistAnimData[spellLevel].part2Last, 14, 112, 0, 0, 0, 0, 0, false); + + mov->close(); + delete mov; + + _screen->setCurPage(cp); + _screen->copyPage(12, 0); + + updateDrawPage2(); + snd_playQueuedEffects(); + return 1; +} + +int LoLEngine::processMagicLightning(int charNum, int spellLevel) { + _screen->hideMouse(); + _screen->copyPage(0, 2); + gui_drawScene(2); + _screen->copyPage(2, 12); + + _lightningCurSfx = _lightningProps[spellLevel].sfxId; + _lightningDiv = _lightningProps[spellLevel].frameDiv; + _lightningFirstSfx = 0; + + Common::String wsafile = Common::String::format("litning%d.wsa", spellLevel + 1); + WSAMovie_v2 *mov = new WSAMovie_v2(this); + mov->open(wsafile.c_str(), 1, 0); + if (!mov->opened()) + error("Litning: Unable to load %s", wsafile.c_str()); + + for (int i = 0; i < 4; i++) + playSpellAnimation(mov, 0, _lightningProps[spellLevel].lastFrame, 3, 93, 0, &LoLEngine::callbackProcessMagicLightning, 0, 0, 0, false); + + mov->close(); + delete mov; + + _screen->setScreenPalette(_screen->getPalette(1)); + _screen->copyPage(12, 2); + _screen->copyPage(12, 0); + updateDrawPage2(); + + static const uint8 lightningDamage[] = { 18, 35, 50, 72 }; + inflictMagicalDamageForBlock(calcNewBlockPosition(_currentBlock, _currentDirection), charNum, lightningDamage[spellLevel], 5); + + _sceneUpdateRequired = true; + gui_drawScene(0); + _screen->showMouse(); + return 1; +} + +int LoLEngine::processMagicFog() { + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 12); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + int numFrames = mov->open("fog.wsa", 0, 0); + if (!mov->opened()) + error("Fog: Unable to load fog.wsa"); + + snd_playSoundEffect(145, -1); + + for (int curFrame = 0; curFrame < numFrames; curFrame++) { + uint32 delayTimer = _system->getMillis() + 3 * _tickLength; + _screen->copyPage(12, 2); + mov->displayFrame(curFrame % numFrames, 2, 112, 0, 0x5000, _transparencyTable1, _transparencyTable2); + _screen->copyRegion(112, 0, 112, 0, 176, 120, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + delayUntil(delayTimer); + } + + mov->close(); + delete mov; + + _screen->copyPage(12, 2); + _screen->setCurPage(cp); + updateDrawPage2(); + + uint16 o = _levelBlockProperties[calcNewBlockPosition(_currentBlock, _currentDirection)].assignedObjects; + while (o & 0x8000) { + inflictMagicalDamage(o, -1, 15, 6, 0); + o = _monsters[o & 0x7FFF].nextAssignedObject; + } + + gui_drawScene(0); + return 1; +} + +int LoLEngine::processMagicSwarm(int charNum, int damage) { + createTransparencyTables(); + + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 12); + snd_playSoundEffect(74, -1); + + uint16 destIds[6]; + uint8 destModes[6]; + int8 destTicks[6]; + + memset(destIds, 0, sizeof(destIds)); + memset(destModes, 8, sizeof(destModes)); + memset(destTicks, 0, sizeof(destTicks)); + + int t = 0; + uint16 o = _levelBlockProperties[calcNewBlockPosition(_currentBlock, _currentDirection)].assignedObjects; + while (o & 0x8000) { + o &= 0x7FFF; + if (_monsters[o].mode != 13) { + destIds[t++] = o; + + if (!(_monsters[o].flags & 0x2000)) { + _envSfxUseQueue = true; + inflictMagicalDamage(o | 0x8000, charNum, damage, 0, 0); + _envSfxUseQueue = false; + _monsters[o].flags &= 0xFFEF; + } + } + o = _monsters[o].nextAssignedObject; + } + + for (int i = 0; i < t; i++) { + SWAP(destModes[i], _monsters[destIds[i]].mode); + SWAP(destTicks[i], _monsters[destIds[i]].fightCurTick); + } + + gui_drawScene(_screen->_curPage); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _screen->_curPage, 7); + + for (int i = 0; i < t; i++) { + _monsters[destIds[i]].mode = destModes[i]; + _monsters[destIds[i]].fightCurTick = destTicks[i]; + } + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + + mov->open("swarm.wsa", 0, 0); + if (!mov->opened()) + error("Swarm: Unable to load SWARM.WSA"); + _screen->hideMouse(); + playSpellAnimation(mov, 0, 37, 2, 0, 0, 0, 0, 0, 0, false); + playSpellAnimation(mov, 38, 41, 8, 0, 0, &LoLEngine::callbackProcessMagicSwarm, 0, 0, 0, false); + _screen->showMouse(); + mov->close(); + + _screen->copyPage(12, 0); + _screen->updateScreen(); + updateDrawPage2(); + + snd_playQueuedEffects(); + + _screen->setCurPage(cp); + delete mov; + return 1; +} + +int LoLEngine::processMagicVaelansCube() { + uint8 *sp1 = _screen->getPalette(1).getData(); + int len = _screen->getPalette(1).getNumColors() * 3; + + uint8 *tmpPal1 = new uint8[len]; + uint8 *tmpPal2 = new uint8[len]; + + memcpy(tmpPal1, sp1, len); + memcpy(tmpPal2, sp1, len); + + if (_flags.use16ColorMode) { + for (int i = 0; i < 16; i++) { + uint16 a = sp1[i * 3 + 1] + 16; + tmpPal2[i * 3 + 1] = (a > 58) ? 58 : a; + tmpPal2[i * 3] = sp1[i * 3]; + a = sp1[i * 3 + 2] + 16; + tmpPal2[i * 3 + 2] = (a > 63) ? 63 : a; + } + } else { + for (int i = 0; i < 128; i++) { + uint16 a = sp1[i * 3] + 16; + tmpPal2[i * 3] = (a > 60) ? 60 : a; + tmpPal2[i * 3 + 1] = sp1[i * 3 + 1]; + a = sp1[i * 3 + 2] + 19; + tmpPal2[i * 3 + 2] = (a > 60) ? 60 : a; + } + } + + snd_playSoundEffect(146, -1); + + uint32 ctime = _system->getMillis(); + uint32 endTime = _system->getMillis() + 70 * _tickLength; + + while (_system->getMillis() < endTime) { + _screen->timedPaletteFadeStep(tmpPal1, tmpPal2, _system->getMillis() - ctime, 70 * _tickLength); + updateInput(); + } + + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + uint8 s = _levelBlockProperties[bl].walls[_currentDirection ^ 2]; + uint8 flg = _wllWallFlags[s]; + + int res = (s == 47 && (_currentLevel == 17 || _currentLevel == 24)) ? 1 : 0; + if ((_wllVmpMap[s] == 1 || _wllVmpMap[s] == 2) && (!(flg & 1)) && (_currentLevel != 22)) { + memset(_levelBlockProperties[bl].walls, 0, 4); + gui_drawScene(0); + res = 1; + } + + uint16 o = _levelBlockProperties[bl].assignedObjects; + while (o & 0x8000) { + LoLMonster *m = &_monsters[o & 0x7FFF]; + if (m->properties->flags & 0x1000) { + inflictDamage(o, 100, 0xFFFF, 0, 0x80); + res = 1; + } + o = m->nextAssignedObject; + } + + ctime = _system->getMillis(); + endTime = _system->getMillis() + 70 * _tickLength; + + while (_system->getMillis() < endTime) { + _screen->timedPaletteFadeStep(tmpPal2, tmpPal1, _system->getMillis() - ctime, 70 * _tickLength); + updateInput(); + } + + delete[] tmpPal1; + delete[] tmpPal2; + + return res; +} + +int LoLEngine::processMagicGuardian(int charNum) { + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 2); + _screen->copyPage(2, 12); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + mov->open("guardian.wsa", 0, 0); + if (!mov->opened()) + error("Guardian: Unable to load guardian.wsa"); + snd_playSoundEffect(156, -1); + playSpellAnimation(mov, 0, 37, 2, 112, 0, 0, 0, 0, 0, false); + + _screen->copyPage(2, 12); + + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + int res = (_levelBlockProperties[bl].assignedObjects & 0x8000) ? 1 : 0; + inflictMagicalDamageForBlock(bl, charNum, 200, 0x80); + + _screen->copyPage(12, 2); + updateDrawPage2(); + gui_drawScene(2); + + _screen->copyPage(2, 12); + snd_playSoundEffect(176, -1); + playSpellAnimation(mov, 38, 48, 8, 112, 0, 0, 0, 0, 0, false); + + mov->close(); + delete mov; + + _screen->setCurPage(cp); + gui_drawPlayField(); + updateDrawPage2(); + return res; +} + +void LoLEngine::callbackProcessMagicSwarm(WSAMovie_v2 *mov, int x, int y) { + if (_swarmSpellStatus) + _screen->copyRegion(112, 0, 112, 0, 176, 120, 6, _screen->_curPage); + _swarmSpellStatus ^= 1; +} + +void LoLEngine::callbackProcessMagicLightning(WSAMovie_v2 *mov, int x, int y) { + if (_lightningDiv == 2) + shakeScene(1, 2, 3, 0); + + const Palette &p1 = _screen->getPalette(1); + + if (_lightningSfxFrame % _lightningDiv) { + _screen->setScreenPalette(p1); + } else { + Palette tpal(p1.getNumColors()); + tpal.copy(p1); + + int start = 6; + int end = 384; + + if (_flags.use16ColorMode) { + start = 3; + end = 48; + } + + for (int i = start; i < end; i++) { + uint16 v = (tpal[i] * 120) / 64; + tpal[i] = (v < 64) ? v : 63; + } + + _screen->setScreenPalette(tpal); + } + + if (_lightningDiv == 2) { + if (!_lightningFirstSfx) { + snd_playSoundEffect(_lightningCurSfx, -1); + _lightningFirstSfx = 1; + } + } else { + if (!(_lightningSfxFrame & 7)) + snd_playSoundEffect(_lightningCurSfx, -1); + } + + _lightningSfxFrame++; +} + +void LoLEngine::drinkBezelCup(int numUses, int charNum) { + createTransparencyTables(); + + int cp = _screen->setCurPage(2); + snd_playSoundEffect(73, -1); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + mov->open("bezel.wsa", 0, 0); + if (!mov->opened()) + error("Bezel: Unable to load bezel.wsa"); + + int x = _activeCharsXpos[charNum] - 11; + int y = 124; + int w = mov->width(); + int h = mov->height(); + + _screen->copyRegion(x, y, 0, 0, w, h, 0, 2, Screen::CR_NO_P_CHECK); + + static const uint8 bezelAnimData[] = { 0, 26, 20, 27, 61, 55, 62, 92, 86, 93, 131, 125 }; + int frm = bezelAnimData[numUses * 3]; + int hpDiff = _characters[charNum].hitPointsMax - _characters[charNum].hitPointsCur; + uint16 step = 0; + + do { + step = (step & 0xFF) + (hpDiff * 256) / (bezelAnimData[numUses * 3 + 1]); + increaseCharacterHitpoints(charNum, step / 256, true); + gui_drawCharPortraitWithStats(charNum); + + uint32 etime = _system->getMillis() + 4 * _tickLength; + + _screen->copyRegion(0, 0, x, y, w, h, 2, 2, Screen::CR_NO_P_CHECK); + mov->displayFrame(frm, 2, x, y, _flags.use16ColorMode ? 0x4000 : 0x5000, _transparencyTable1, _transparencyTable2); + _screen->copyRegion(x, y, x, y, w, h, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + delayUntil(etime); + } while (++frm < bezelAnimData[numUses * 3 + 1]); + + _characters[charNum].hitPointsCur = _characters[charNum].hitPointsMax; + _screen->copyRegion(0, 0, x, y, w, h, 2, 2, Screen::CR_NO_P_CHECK); + removeCharacterEffects(&_characters[charNum], 4, 4); + gui_drawCharPortraitWithStats(charNum); + _screen->copyRegion(x, y, x, y, w, h, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + mov->close(); + delete mov; + + _screen->setCurPage(cp); +} + +void LoLEngine::addSpellToScroll(int spell, int charNum) { + bool assigned = false; + int slot = 0; + for (int i = 0; i < 7; i++) { + if (!assigned && _availableSpells[i] == -1) { + assigned = true; + slot = i; + } + + if (_availableSpells[i] == spell) { + _txt->printMessage(2, "%s", getLangString(0x42D0)); + return; + } + } + + if (spell > 1) + transferSpellToScollAnimation(charNum, spell, slot - 1); + + _availableSpells[slot] = spell; + gui_enableDefaultPlayfieldButtons(); +} + +void LoLEngine::transferSpellToScollAnimation(int charNum, int spell, int slot) { + int cX = 16 + _activeCharsXpos[charNum]; + + if (slot != 1) { + _screen->loadBitmap("playfld.cps", 3, 3, 0); + _screen->copyRegion(8, 0, 216, 0, 96, 120, 3, 3, Screen::CR_NO_P_CHECK); + _screen->copyPage(3, 10); + for (int i = 0; i < 9; i++) { + int h = (slot + 1) * 9 + i + 1; + uint32 delayTimer = _system->getMillis() + _tickLength; + _screen->copyPage(10, 3); + _screen->copyRegion(216, 0, 8, 0, 96, 120, 3, 3, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 12, 0, 87, 15, 2, 2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(201, 1, 17, 15, 6, h, 2, 2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(208, 1, 89, 15, 6, h, 2, 2, Screen::CR_NO_P_CHECK); + int cp = _screen->setCurPage(2); + _screen->fillRect(21, 15, 89, h + 15, _flags.use16ColorMode ? 0xBB : 206); + _screen->copyRegion(112, 16, 12, h + 15, 87, 14, 2, 2, Screen::CR_NO_P_CHECK); + + int y = 15; + Screen::FontId of = _screen->setFont(Screen::FID_9_FNT); + for (int ii = 0; ii < 7; ii++) { + if (_availableSpells[ii] == -1) + continue; + uint8 col = (ii == _selectedSpell) ? 132 : 1; + if (_flags.use16ColorMode) + col = (ii == _selectedSpell) ? 0x88 : 0x44; + _screen->fprintString("%s", 24, y, col, 0, 0, getLangString(_spellProperties[_availableSpells[ii]].spellNameCode)); + y += 9; + } + _screen->setFont(of); + + _screen->setCurPage(cp); + _screen->copyRegion(8, 0, 8, 0, 96, 120, 3, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + delayUntil(delayTimer); + } + } + + _screen->hideMouse(); + + _screen->copyPage(0, 12); + int vX = _updateSpellBookCoords[slot << 1] + 32; + int vY = _updateSpellBookCoords[(slot << 1) + 1] + 5; + + Common::String wsaFile = Common::String::format("write%0d", spell); + if (_flags.isTalkie) + wsaFile += (_lang == 1) ? 'f' : (_lang == 0 ? 'e' : 'g'); + wsaFile += ".wsa"; + snd_playSoundEffect(_updateSpellBookAnimData[(spell << 2) + 3], -1); + snd_playSoundEffect(95, -1); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + + mov->open("getspell.wsa", 0, 0); + if (!mov->opened()) + error("SpellBook: Unable to load getspell anim"); + snd_playSoundEffect(128, -1); + playSpellAnimation(mov, 0, 25, 5, _activeCharsXpos[charNum], 148, 0, 0, 0, 0, true); + snd_playSoundEffect(128, -1); + playSpellAnimation(mov, 26, 52, 5, _activeCharsXpos[charNum], 148, 0, 0, 0, 0, true); + + for (int i = 16; i > 0; i--) { + uint32 delayTimer = _system->getMillis() + _tickLength; + _screen->copyPage(12, 2); + + int wsaX = vX + (((((cX - vX) << 8) / 16) * i) >> 8) - 16; + int wsaY = vY + (((((160 - vY) << 8) / 16) * i) >> 8) - 16; + + mov->displayFrame(51, 2, wsaX, wsaY, 0x5000, _transparencyTable1, _transparencyTable2); + + _screen->copyRegion(wsaX, wsaY, wsaX, wsaY, mov->width() + 48, mov->height() + 48, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + delayUntil(delayTimer); + } + + mov->close(); + + mov->open("spellexp.wsa", 0, 0); + if (!mov->opened()) + error("SpellBook: Unable to load spellexp anim"); + snd_playSoundEffect(168, -1); + playSpellAnimation(mov, 0, 8, 3, vX - 44, vY - 38, 0, 0, 0, 0, true); + mov->close(); + + mov->open("writing.wsa", 0, 0); + if (!mov->opened()) + error("SpellBook: Unable to load writing anim"); + playSpellAnimation(mov, 0, 6, 5, _updateSpellBookCoords[slot << 1], _updateSpellBookCoords[(slot << 1) + 1], 0, 0, 0, 0, false); + mov->close(); + + mov->open(wsaFile.c_str(), 0, 0); + if (!mov->opened()) + error("SpellBook: Unable to load spellbook anim"); + snd_playSoundEffect(_updateSpellBookAnimData[(spell << 2) + 3], -1); + playSpellAnimation(mov, _updateSpellBookAnimData[(spell << 2) + 1], _updateSpellBookAnimData[(spell << 2) + 2], _updateSpellBookAnimData[spell << 2], _updateSpellBookCoords[slot << 1], _updateSpellBookCoords[(slot << 1) + 1], 0, 0, 0, 0, false); + mov->close(); + + gui_drawScene(2); + updateDrawPage2(); + + _screen->showMouse(); + + delete mov; +} + +void LoLEngine::playSpellAnimation(WSAMovie_v2 *mov, int firstFrame, int lastFrame, int frameDelay, int x, int y, SpellProcCallback callback, uint8 *pal1, uint8 *pal2, int fadeDelay, bool restoreScreen) { + int w = 0; + int h = 0; + + if (mov) { + w = mov->width(); + h = mov->height(); + } + + int w2 = w; + int h2 = h; + uint32 startTime = _system->getMillis(); + + if (x < 0) + w2 += x; + if (y < 0) + h2 += y; + + int dir = lastFrame >= firstFrame ? 1 : -1; + int curFrame = firstFrame; + + bool fin = false; + + while (!fin) { + uint32 delayTimer = _system->getMillis() + _tickLength * frameDelay; + + if (mov || callback) + _screen->copyPage(12, 2); + + if (callback) + (this->*callback)(mov, x, y); + + if (mov) + mov->displayFrame(curFrame % mov->frames(), 2, x, y, _flags.use16ColorMode ? 0x4000 : 0x5000, _transparencyTable1, _transparencyTable2); + + if (mov || callback) { + _screen->copyRegion(x, y, x, y, w2, h2, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + uint32 tm = _system->getMillis(); + uint32 del = (delayTimer > tm) ? (delayTimer - tm) : 0; + + do { + uint32 step = del > _tickLength ? _tickLength : del; + + if (!pal1 || !pal2) { + if (del) { + delay(step); + del -= step; + } else { + updateInput(); + } + continue; + } + + if (!_screen->timedPaletteFadeStep(pal1, pal2, _system->getMillis() - startTime, _tickLength * fadeDelay) && !mov) + return; + + if (del) { + delay(step); + del -= step; + } else { + updateInput(); + } + } while (del); + + if (!mov) + continue; + + curFrame += dir; + if ((dir > 0 && curFrame >= lastFrame) || (dir < 0 && curFrame < lastFrame)) + fin = true; + } + + if (restoreScreen && (mov || callback)) { + _screen->copyPage(12, 2); + _screen->copyRegion(x, y, x, y, w2, h2, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } +} + +int LoLEngine::checkMagic(int charNum, int spellNum, int spellLevel) { + if (_spellProperties[spellNum].mpRequired[spellLevel] > _characters[charNum].magicPointsCur) { + if (characterSays(0x4043, _characters[charNum].id, true)) + _txt->printMessage(6, getLangString(0x4043), _characters[charNum].name); + return 1; + } else if (_spellProperties[spellNum].hpRequired[spellLevel] >= _characters[charNum].hitPointsCur) { + _txt->printMessage(2, getLangString(0x4179), _characters[charNum].name); + return 1; + } + + return 0; +} + +int LoLEngine::getSpellTargetBlock(int currentBlock, int direction, int maxDistance, uint16 &targetBlock) { + targetBlock = 0xFFFF; + uint16 c = calcNewBlockPosition(currentBlock, direction); + + int i = 0; + for (; i < maxDistance; i++) { + if (_levelBlockProperties[currentBlock].assignedObjects & 0x8000) { + targetBlock = currentBlock; + return i; + } + + if (_wllWallFlags[_levelBlockProperties[c].walls[direction ^ 2]] & 7) { + targetBlock = c; + return i; + } + + currentBlock = c; + c = calcNewBlockPosition(currentBlock, direction); + } + + return i; +} + +void LoLEngine::inflictMagicalDamage(int target, int attacker, int damage, int index, int hitType) { + hitType = hitType ? 1 : 2; + damage = calcInflictableDamagePerItem(attacker, target, damage, index, hitType); + inflictDamage(target, damage, attacker, 2, index); +} + +void LoLEngine::inflictMagicalDamageForBlock(int block, int attacker, int damage, int index) { + uint16 o = _levelBlockProperties[block].assignedObjects; + while (o & 0x8000) { + inflictDamage(o, calcInflictableDamagePerItem(attacker, o, damage, index, 2), attacker, 2, index); + if ((_monsters[o & 0x7FFF].flags & 0x20) && (_currentLevel != 22)) + break; + o = _monsters[o & 0x7FFF].nextAssignedObject; + } +} + +// fight + +int LoLEngine::battleHitSkillTest(int16 attacker, int16 target, int skill) { + if (target == -1) + return 0; + if (attacker == -1) + return 1; + + if (target & 0x8000) { + if (_monsters[target & 0x7FFF].mode >= 13) + return 0; + } + + uint16 hitChanceModifier = 0; + uint16 evadeChanceModifier = 0; + int sk = 0; + + if (attacker & 0x8000) { + hitChanceModifier = _monsters[target & 0x7FFF].properties->fightingStats[0]; + sk = 100 - _monsters[target & 0x7FFF].properties->skillLevel; + } else { + hitChanceModifier = _characters[attacker].defaultModifiers[0]; + int8 m = _characters[attacker].skillModifiers[skill]; + if (skill == 1) + m *= 3; + sk = 100 - (_characters[attacker].skillLevels[skill] + m); + } + + if (target & 0x8000) { + evadeChanceModifier = _monsters[target & 0x7FFF].properties->fightingStats[3]; + if (_monsterModifiers4) + evadeChanceModifier = (evadeChanceModifier * _monsterModifiers4[_monsterDifficulty]) >> 8; + _monsters[target & 0x7FFF].flags |= 0x10; + } else { + evadeChanceModifier = _characters[target].defaultModifiers[3]; + } + + int r = rollDice(1, 100); + if (r >= sk) + return 2; + + uint16 v = (evadeChanceModifier << 8) / hitChanceModifier; + + if (r < v) + return 0; + + return 1; +} + +int LoLEngine::calcInflictableDamage(int16 attacker, int16 target, int hitType) { + const uint16 *s = getCharacterOrMonsterItemsMight(attacker); + + // The original code looks somewhat like the commented out part of the next line. + // In the end the value is always set to zero. I do not know whether this is done on purpose or not. + // It might be a bug in the original code. + int res = 0/*attacker & 0x8000 ? 0 : _characters[attacker].might*/; + for (int i = 0; i < 8; i++) + res += calcInflictableDamagePerItem(attacker, target, s[i], i, hitType); + + return res; +} + +int LoLEngine::inflictDamage(uint16 target, int damage, uint16 attacker, int skill, int flags) { + LoLMonster *m = 0; + LoLCharacter *c = 0; + + if (target & 0x8000) { + m = &_monsters[target & 0x7FFF]; + if (m->mode >= 13) + return 0; + + if (damage > 0) { + m->hitPoints -= damage; + m->damageReceived = 0x8000 | damage; + m->flags |= 0x10; + m->hitOffsX = rollDice(1, 24); + m->hitOffsX -= 12; + m->hitOffsY = rollDice(1, 24); + m->hitOffsY -= 12; + m->hitPoints = CLIP(m->hitPoints, 0, m->properties->hitPoints); + + if (!(attacker & 0x8000)) + applyMonsterDefenseSkill(m, attacker, flags, skill, damage); + + snd_queueEnvironmentalSoundEffect(m->properties->sounds[2], m->block); + checkSceneUpdateNeed(m->block); + + if (m->hitPoints <= 0) { + m->hitPoints = 0; + if (!(attacker & 0x8000)) + increaseExperience(attacker, skill, m->properties->hitPoints); + setMonsterMode(m, 13); + } + } else { + m->hitPoints -= damage; + m->hitPoints = CLIP(m->hitPoints, 1, m->properties->hitPoints); + } + + } else { + if (target > 3) { + // WORKAROUND for script bug + int i = 0; + for (; i < 4; i++) { + if (_characters[i].id == target) { + target = i; + break; + } + } + if (i == 4) + return 0; + } + + c = &_characters[target]; + if (!(c->flags & 1) || (c->flags & 8)) + return 0; + + if (!(c->flags & 0x1000)) + snd_playSoundEffect(c->screamSfx, -1); + + setTemporaryFaceFrame(target, 6, 4, 0); + + // check for equipped cloud ring + if (flags == 4 && itemEquipped(target, 229)) + damage >>= 2; + + setCharacterMagicOrHitPoints(target, 0, -damage, 1); + + if (c->hitPointsCur <= 0) { + characterHitpointsZero(target, flags); + } else { + _characters[target].damageSuffered = damage; + setCharacterUpdateEvent(target, 2, 4, 1); + } + gui_drawCharPortraitWithStats(target); + } + + if (!(attacker & 0x8000)) { + if (!skill) + _characters[attacker].weaponHit = damage; + increaseExperience(attacker, skill, damage); + } + + return damage; +} + +void LoLEngine::characterHitpointsZero(int16 charNum, int flags) { + LoLCharacter *c = &_characters[charNum]; + c->hitPointsCur = 0; + c->flags |= 8; + removeCharacterEffects(c, 1, 5); + _partyDamageFlags = flags; +} + +void LoLEngine::removeCharacterEffects(LoLCharacter *c, int first, int last) { + for (int i = first; i <= last; i++) { + switch (i - 1) { + case 0: + c->flags &= 0xFFFB; + c->weaponHit = 0; + break; + + case 1: + c->damageSuffered = 0; + break; + + case 2: + c->flags &= 0xFFBF; + break; + + case 3: + c->flags &= 0xFF7F; + break; + + case 4: + c->flags &= 0xFEFF; + break; + + case 6: + c->flags &= 0xEFFF; + break; + + default: + break; + } + + for (int ii = 0; ii < 5; ii++) { + if (i != c->characterUpdateEvents[ii]) + continue; + + c->characterUpdateEvents[ii] = 0; + c->characterUpdateDelay[ii] = 0; + } + } + + _timer->enable(3); +} + +int LoLEngine::calcInflictableDamagePerItem(int16 attacker, int16 target, uint16 itemMight, int index, int hitType) { + int dmg = (attacker == -1) ? 0x100 : getCharacterOrMonsterStats(attacker)[1]; + const uint16 *st_t = getCharacterOrMonsterProtectionAgainstItems(target); + + dmg = (dmg * itemMight) >> 8; + if (!dmg) + return 0; + + if (!(attacker & 0x8000)) { + dmg = (dmg * _characters[attacker].totalMightModifier) >> 8; + if (!dmg) + return 0; + } + + int d = (index & 0x80) ? st_t[7] : st_t[index]; + int r = (dmg * ABS(d)) >> 8; + dmg = d < 0 ? -r : r; + + if (hitType == 2 || !dmg) + return (dmg == 1) ? 2 : dmg; + + + int p = (calculateProtection(target) << 7) / dmg; + if (p > 217) + p = 217; + + d = 256 - p; + r = (dmg * ABS(d)) >> 8; + dmg = d < 0 ? -r : r; + + return (dmg < 2) ? 2 : dmg; +} + +void LoLEngine::checkForPartyDeath() { + Button b; + b.data0Val2 = b.data1Val2 = b.data2Val2 = 0xFE; + b.data0Val3 = b.data1Val3 = b.data2Val3 = 0x01; + + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1) || _characters[i].hitPointsCur <= 0) + continue; + return; + } + + if (_weaponsDisabled) + clickedExitCharInventory(&b); + + gui_drawAllCharPortraitsWithStats(); + + if (_partyDamageFlags & 0x40) { + _screen->fadeToBlack(40); + for (int i = 0; i < 4; i++) { + if (_characters[i].flags & 1) + increaseCharacterHitpoints(i, 1, true); + } + gui_drawAllCharPortraitsWithStats(); + _screen->fadeToPalette1(40); + + } else { + if (!_flags.use16ColorMode) + _screen->fadeClearSceneWindow(10); + restoreAfterSpecialScene(0, 1, 1, 0); + + snd_playTrack(325); + stopPortraitSpeechAnim(); + initTextFading(0, 1); + setMouseCursorToIcon(0); + _updateFlags |= 4; + setLampMode(true); + disableSysTimer(2); + + _gui->runMenu(_gui->_deathMenu); + + setMouseCursorToItemInHand(); + _updateFlags &= 0xFFFB; + resetLampStatus(); + + gui_enableDefaultPlayfieldButtons(); + enableSysTimer(2); + updateDrawPage2(); + } +} + +void LoLEngine::applyMonsterAttackSkill(LoLMonster *monster, int16 target, int16 damage) { + if (rollDice(1, 100) > monster->properties->attackSkillChance) + return; + + int t = 0; + + switch (monster->properties->attackSkillType - 1) { + case 0: + t = removeCharacterItem(target, 0x7FF); + if (t) { + giveItemToMonster(monster, t); + if (characterSays(0x4019, _characters[target].id, true)) + _txt->printMessage(6, "%s", getLangString(0x4019)); + } + break; + + case 1: + // poison character + paralyzePoisonCharacter(target, 0x80, 0x88, 100, 1); + break; + + case 2: + t = removeCharacterItem(target, 0x20); + if (t) { + deleteItem(t); + if (characterSays(0x401B, _characters[target].id, true)) + _txt->printMessage(6, "%s", getLangString(0x401B)); + } + break; + + case 3: + t = removeCharacterItem(target, 0x0F); + if (t) { + if (characterSays(0x401E, _characters[target].id, true)) + _txt->printMessage(6, getLangString(0x401E), _characters[target].name); + setItemPosition(t, monster->x, monster->y, 0, 1); + } + break; + + case 5: + if (_characters[target].magicPointsCur <= 0) + return; + + monster->hitPoints += _characters[target].magicPointsCur; + _characters[target].magicPointsCur = 0; + gui_drawCharPortraitWithStats(target); + if (characterSays(0x4020, _characters[target].id, true)) + _txt->printMessage(6, getLangString(0x4020), _characters[target].name); + break; + + case 7: + stunCharacter(target); + break; + + case 8: + monster->hitPoints += damage; + if (monster->hitPoints > monster->properties->hitPoints) + monster->hitPoints = monster->properties->hitPoints; + break; + + case 9: + // paralyze party (spider web) + paralyzePoisonAllCharacters(0x40, 0x48, 100); + break; + + default: + break; + } +} + +void LoLEngine::applyMonsterDefenseSkill(LoLMonster *monster, int16 attacker, int flags, int skill, int damage) { + if (rollDice(1, 100) > monster->properties->defenseSkillChance) + return; + + int itm = 0; + + switch (monster->properties->defenseSkillType - 1) { + case 0: + case 1: + if ((flags & 0x3F) == 2 || skill) + return; + + for (int i = 0; i < 3; i++) { + itm = _characters[attacker].items[i]; + if (!itm) + continue; + if ((_itemProperties[_itemsInPlay[itm].itemPropertyIndex].protection & 0x3F) != flags) + continue; + + removeCharacterItem(attacker, 0x7FFF); + + if (monster->properties->defenseSkillType == 1) { + giveItemToMonster(monster, itm); + if (characterSays(0x401C, _characters[attacker].id, true)) + _txt->printMessage(6, "%s", getLangString(0x401C)); + + } else { + deleteItem(itm); + if (characterSays(0x401D, _characters[attacker].id, true)) + _txt->printMessage(6, "%s", getLangString(0x401D)); + } + } + break; + + case 2: + if (!(flags & 0x80)) + return; + monster->flags |= 8; + monster->direction = calcMonsterDirection(monster->x, monster->y, _partyPosX, _partyPosY) ^ 4; + setMonsterMode(monster, 9); + monster->fightCurTick = 30; + break; + + case 3: + if (flags != 3) + return; + monster->hitPoints += damage; + if (monster->hitPoints > monster->properties->hitPoints) + monster->hitPoints = monster->properties->hitPoints; + break; + + case 4: + if (!(flags & 0x80)) + return; + monster->hitPoints += damage; + if (monster->hitPoints > monster->properties->hitPoints) + monster->hitPoints = monster->properties->hitPoints; + break; + + case 5: + if ((flags & 0x84) == 0x84) + monster->numDistAttacks++; + break; + + default: + break; + } +} + +int LoLEngine::removeCharacterItem(int charNum, int itemFlags) { + for (int i = 0; i < 11; i++) { + int s = _characters[charNum].items[i]; + if (!((1 << i) & itemFlags) || !s) + continue; + + _characters[charNum].items[i] = 0; + runItemScript(charNum, s, 0x100, 0, 0); + + return s; + } + + return 0; +} + +int LoLEngine::paralyzePoisonCharacter(int charNum, int typeFlag, int immunityFlags, int hitChance, int redraw) { + if (!(_characters[charNum].flags & 1) || (_characters[charNum].flags & immunityFlags)) + return 0; + + if (rollDice(1, 100) > hitChance) + return 0; + + int r = 0; + + if (typeFlag == 0x40) { + _characters[charNum].flags |= 0x40; + setCharacterUpdateEvent(charNum, 3, 3600, 1); + r = 1; + + // check for bezel ring + } else if (typeFlag == 0x80 && !itemEquipped(charNum, 225)) { + _characters[charNum].flags |= 0x80; + setCharacterUpdateEvent(charNum, 4, 10, 1); + if (characterSays(0x4021, _characters[charNum].id, true)) + _txt->printMessage(6, getLangString(0x4021), _characters[charNum].name); + r = 1; + + } else if (typeFlag == 0x1000) { + _characters[charNum].flags |= 0x1000; + setCharacterUpdateEvent(charNum, 7, 120, 1); + r = 1; + } + + if (r && redraw) + gui_drawCharPortraitWithStats(charNum); + + return r; +} + +void LoLEngine::paralyzePoisonAllCharacters(int typeFlag, int immunityFlags, int hitChance) { + bool r = false; + for (int i = 0; i < 4; i++) { + if (paralyzePoisonCharacter(i, typeFlag, immunityFlags, hitChance, 0)) + r = true; + } + if (r) + gui_drawAllCharPortraitsWithStats(); +} + +void LoLEngine::stunCharacter(int charNum) { + if (!(_characters[charNum].flags & 1) || (_characters[charNum].flags & 0x108)) + return; + + _characters[charNum].flags |= 0x100; + + setCharacterUpdateEvent(charNum, 5, 20, 1); + gui_drawCharPortraitWithStats(charNum); + + _txt->printMessage(6, getLangString(0x4026), _characters[charNum].name); +} + +void LoLEngine::restoreSwampPalette() { + _flagsTable[52] &= 0xFB; + if (_currentLevel != 11) + return; + + uint8 *s = _screen->getPalette(2).getData(); + uint8 *d = _screen->getPalette(0).getData(); + uint8 *d2 = _screen->getPalette(1).getData(); + + for (int i = 1; i < (_screen->getPalette(0).getNumColors() * 3); i++) + SWAP(s[i], d[i]); + + generateBrightnessPalette(_screen->getPalette(0), _screen->getPalette(1), _brightness, _lampEffect); + _screen->loadSpecialColors(_screen->getPalette(2)); + _screen->loadSpecialColors(_screen->getPalette(1)); + + playSpellAnimation(0, 0, 0, 2, 0, 0, 0, s, d2, 40, 0); +} + +void LoLEngine::launchMagicViper() { + _partyAwake = true; + + int d = 0; + for (uint16 b = _currentBlock; d < 3; d++) { + uint16 o = _levelBlockProperties[b].assignedObjects; + if (o & 0x8000) + break; + b = calcNewBlockPosition(b, _currentDirection); + if (_wllWallFlags[_levelBlockProperties[b].walls[_currentDirection ^ 2]] & 7) + break; + } + + _screen->copyPage(0, 12); + snd_playSoundEffect(148, -1); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + int numFrames = mov->open("viper.wsa", 1, 0); + if (!mov->opened()) + error("Viper: Unable to load viper.wsa"); + + static const uint8 viperAnimData[] = { 15, 25, 20, 10, 25, 20, 5, 25, 20, 0, 25, 20 }; + const uint8 *v = &viperAnimData[d * 3]; + int frm = v[0]; + + for (bool running = true; running;) { + uint32 etime = _system->getMillis() + 5 * _tickLength; + _screen->copyPage(12, 2); + + if (frm == v[2]) + snd_playSoundEffect(172, -1); + + mov->displayFrame(frm++ % numFrames, 2, 112, 0, 0x5000, _transparencyTable1, _transparencyTable2); + _screen->copyRegion(112, 0, 112, 0, 176, 120, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + delayUntil(etime); + + if (frm > v[1]) + running = false; + } + + mov->close(); + delete mov; + + _screen->copyPage(12, 0); + _screen->copyPage(12, 2); + + int t = rollDice(1, 4); + + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1)) { + t = t % 4; + continue; + } + inflictDamage(t, _currentLevel + 10, 0x8000, 2, 0x86); + } +} + +void LoLEngine::breakIceWall(uint8 *pal1, uint8 *pal2) { + _screen->hideMouse(); + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + _levelBlockProperties[bl].flags &= 0xEF; + _screen->copyPage(0, 2); + gui_drawScene(2); + _screen->copyPage(2, 10); + + WSAMovie_v2 *mov = new WSAMovie_v2(this); + int numFrames = mov->open("shatter.wsa", 1, 0); + if (!mov->opened()) + error("Shatter: Unable to load shatter.wsa"); + snd_playSoundEffect(166, -1); + playSpellAnimation(mov, 0, numFrames, 1, 58, 0, 0, pal1, pal2, 20, true); + mov->close(); + delete mov; + + _screen->copyPage(10, 0); + updateDrawPage2(); + gui_drawScene(0); + _screen->showMouse(); +} + +uint16 LoLEngine::getNearestMonsterFromCharacter(int charNum) { + return getNearestMonsterFromCharacterForBlock(calcNewBlockPosition(_currentBlock, _currentDirection), charNum); +} + +uint16 LoLEngine::getNearestMonsterFromCharacterForBlock(uint16 block, int charNum) { + uint16 cX = 0; + uint16 cY = 0; + + uint16 id = 0xFFFF; + int minDist = 0x7FFF; + + if (block == 0xFFFF) + return id; + + calcCoordinatesForSingleCharacter(charNum, cX, cY); + + int o = _levelBlockProperties[block].assignedObjects; + + while (o & 0x8000) { + LoLMonster *m = &_monsters[o & 0x7FFF]; + if (m->mode >= 13) { + o = m->nextAssignedObject; + continue; + } + + int d = ABS(cX - m->x) + ABS(cY - m->y); + if (d < minDist) { + minDist = d; + id = o; + } + + o = m->nextAssignedObject; + } + + return id; +} + +uint16 LoLEngine::getNearestMonsterFromPos(int x, int y) { + uint16 id = 0xFFFF; + int minDist = 0x7FFF; + + for (int i = 0; i < 30; i++) { + if (_monsters[i].mode > 13) + continue; + + int d = ABS(x - _monsters[i].x) + ABS(y - _monsters[i].y); + if (d < minDist) { + minDist = d; + id = 0x8000 | i; + } + } + + return id; +} + +uint16 LoLEngine::getNearestPartyMemberFromPos(int x, int y) { + uint16 id = 0xFFFF; + int minDist = 0x7FFF; + + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1) || _characters[i].hitPointsCur <= 0) + continue; + + uint16 charX = 0; + uint16 charY = 0; + calcCoordinatesForSingleCharacter(i, charX, charY); + + int d = ABS(x - charX) + ABS(y - charY); + if (d < minDist) { + minDist = d; + id = i; + } + } + + return id; +} + +// magic atlas + +void LoLEngine::displayAutomap() { + snd_playSoundEffect(105, -1); + gui_toggleButtonDisplayMode(_flags.isTalkie ? 78 : 76, 1); + + _currentMapLevel = _currentLevel; + uint8 *tmpWll = new uint8[80]; + memcpy(tmpWll, _wllAutomapData, 80); + + _screen->loadBitmap("parch.cps", 2, 2, &_screen->getPalette(3)); + _screen->loadBitmap("autobut.shp", 3, 5, 0); + const uint8 *shp = _screen->getCPagePtr(5); + + for (int i = 0; i < 109; i++) + _automapShapes[i] = _screen->getPtrToShape(shp, i + 11); + + if (_flags.use16ColorMode) { + static const uint8 ovlSrc[] = { 0x00, 0xEE, 0xCC, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0x22, 0x11, 0xDD, 0xEE, 0xCC }; + memset(_mapOverlay, 0, 256); + for (int i = 0; i < 16; i++) + _mapOverlay[(i << 4) | i] = ovlSrc[i]; + } else + _screen->generateGrayOverlay(_screen->getPalette(3), _mapOverlay, 52, 0, 0, 0, 256, false); + + _screen->loadFont(Screen::FID_9_FNT, "FONT9PN.FNT"); + _screen->loadFont(Screen::FID_6_FNT, "FONT6PN.FNT"); + + for (int i = 0; i < 11; i++) + _defaultLegendData[i].enable = false; + + disableSysTimer(2); + generateTempData(); + resetItems(1); + disableMonsters(); + + bool exitAutomap = false; + _mapUpdateNeeded = false; + + restoreBlockTempData(_currentMapLevel); + loadMapLegendData(_currentMapLevel); + _screen->fadeToBlack(10); + drawMapPage(2); + + _screen->copyPage(2, 0); + _screen->updateScreen(); + _screen->fadePalette(_screen->getPalette(3), 10); + uint32 delayTimer = _system->getMillis() + 8 * _tickLength; + + while (!exitAutomap && !shouldQuit()) { + if (_mapUpdateNeeded) { + drawMapPage(2); + _screen->copyPage(2, 0); + _screen->updateScreen(); + _mapUpdateNeeded = false; + } + + if (_system->getMillis() >= delayTimer) { + redrawMapCursor(); + delayTimer = _system->getMillis() + 8 * _tickLength; + } + + int f = checkInput(0) & 0xFF; + removeInputTop(); + + if (f) { + exitAutomap = automapProcessButtons(f); + gui_notifyButtonListChanged(); + } + + if (f == _keyMap[Common::KEYCODE_c]) { + for (int i = 0; i < 1024; i++) + _levelBlockProperties[i].flags |= 7; + _mapUpdateNeeded = true; + } else if (f == _keyMap[Common::KEYCODE_ESCAPE]) { + exitAutomap = true; + } + + delay(_tickLength); + } + + _screen->loadFont(Screen::FID_9_FNT, "FONT9P.FNT"); + _screen->loadFont(Screen::FID_6_FNT, "FONT6P.FNT"); + + if (_flags.use16ColorMode) + _screen->clearPage(2); + + _screen->fadeToBlack(10); + loadLevelWallData(_currentLevel, false); + memcpy(_wllAutomapData, tmpWll, 80); + delete[] tmpWll; + restoreBlockTempData(_currentLevel); + addLevelItems(); + gui_notifyButtonListChanged(); + enableSysTimer(2); +} + +void LoLEngine::updateAutoMap(uint16 block) { + if (!(_flagsTable[31] & 0x10)) + return; + _levelBlockProperties[block].flags |= 7; + + uint16 x = block & 0x1F; + uint16 y = block >> 5; + + updateAutoMapIntern(block, x, y, -1, -1); + updateAutoMapIntern(block, x, y, 1, -1); + updateAutoMapIntern(block, x, y, -1, 1); + updateAutoMapIntern(block, x, y, 1, 1); + updateAutoMapIntern(block, x, y, 0, -1); + updateAutoMapIntern(block, x, y, 0, 1); + updateAutoMapIntern(block, x, y, -1, 0); + updateAutoMapIntern(block, x, y, 1, 0); +} + +bool LoLEngine::updateAutoMapIntern(uint16 block, uint16 x, uint16 y, int16 xOffs, int16 yOffs) { + static const int16 blockPosTable[] = { 1, -1, 3, 2, -1, 0, -1, 0, 1, -32, 0, 32 }; + x += xOffs; + y += yOffs; + + if ((x & 0xFFE0) || (y & 0xFFE0)) + return false; + + xOffs++; + yOffs++; + + int16 fx = blockPosTable[xOffs]; + uint16 b = block + blockPosTable[6 + xOffs]; + + if (fx != -1) { + if (_wllAutomapData[_levelBlockProperties[b].walls[fx]] & 0xC0) + return false; + } + + int16 fy = blockPosTable[3 + yOffs]; + b = block + blockPosTable[9 + yOffs]; + + if (fy != -1) { + if (_wllAutomapData[_levelBlockProperties[b].walls[fy]] & 0xC0) + return false; + } + + b = block + blockPosTable[6 + xOffs] + blockPosTable[9 + yOffs]; + + if ((fx != -1) && (fy != -1) && (_wllAutomapData[_levelBlockProperties[b].walls[fx]] & 0xC0) && (_wllAutomapData[_levelBlockProperties[b].walls[fy]] & 0xC0)) + return false; + + _levelBlockProperties[b].flags |= 7; + + return true; +} + +void LoLEngine::loadMapLegendData(int level) { + uint16 *legendData = (uint16 *)_tempBuffer5120; + for (int i = 0; i < 32; i++) { + legendData[i * 6] = 0xFFFF; + legendData[i * 6 + 5] = 0xFFFF; + } + + Common::String file = Common::String::format("level%d.xxx", level); + uint32 size = 0; + uint8 *data = _res->fileData(file.c_str(), &size); + uint8 *pos = data; + size = MIN(size / 12, 32); + + for (uint32 i = 0; i < size; i++) { + uint16 *l = &legendData[i * 6]; + l[3] = READ_LE_UINT16(pos); + pos += 2; + l[4] = READ_LE_UINT16(pos); + pos += 2; + l[5] = READ_LE_UINT16(pos); + pos += 2; + l[0] = READ_LE_UINT16(pos); + pos += 2; + l[1] = READ_LE_UINT16(pos); + pos += 2; + l[2] = READ_LE_UINT16(pos); + pos += 2; + } + + delete[] data; +} + +void LoLEngine::drawMapPage(int pageNum) { + // WORKAROUND for French version. The text does not always properly fit the screen there. + const int8 xOffset = (_lang == 1) ? -2 : 0; + + if (_flags.use16ColorMode) + _screen->clearPage(pageNum); + + for (int i = 0; i < 2; i++) { + _screen->loadBitmap("parch.cps", pageNum, pageNum, &_screen->getPalette(3)); + if (_lang == 1) + _screen->copyRegion(236, 16, 236 + xOffset, 16, -xOffset, 1, pageNum, pageNum, Screen::CR_NO_P_CHECK); + + int cp = _screen->setCurPage(pageNum); + Screen::FontId of = _screen->setFont((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? Screen::FID_SJIS_FNT : Screen::FID_9_FNT); + _screen->printText(getLangString(_autoMapStrings[_currentMapLevel]), 236 + xOffset, 8, 1, 0); + uint16 blX = mapGetStartPosX(); + uint16 bl = (mapGetStartPosY() << 5) + blX; + + int sx = _automapTopLeftX; + int sy = _automapTopLeftY; + + for (; bl < 1024; bl++) { + uint8 *w = _levelBlockProperties[bl].walls; + if ((_levelBlockProperties[bl].flags & 7) == 7 && (!(_wllAutomapData[w[0]] & 0xC0)) && (!(_wllAutomapData[w[2]] & 0xC0)) && (!(_wllAutomapData[w[1]] & 0xC0)) && (!(_wllAutomapData[w[3]] & 0xC0))) { + uint16 b0 = calcNewBlockPosition(bl, 0); + uint16 b2 = calcNewBlockPosition(bl, 2); + uint16 b1 = calcNewBlockPosition(bl, 1); + uint16 b3 = calcNewBlockPosition(bl, 3); + + uint8 w02 = _levelBlockProperties[b0].walls[2]; + uint8 w20 = _levelBlockProperties[b2].walls[0]; + uint8 w13 = _levelBlockProperties[b1].walls[3]; + uint8 w31 = _levelBlockProperties[b3].walls[1]; + + // draw block + _screen->copyBlockAndApplyOverlay(_screen->_curPage, sx, sy, _screen->_curPage, sx, sy, 7, 6, 0, _mapOverlay); + + // draw north wall + drawMapBlockWall(b3, w31, sx, sy, 3); + drawMapShape(w31, sx, sy, 3); + if (_wllAutomapData[w31] & 0xC0) + _screen->copyBlockAndApplyOverlay(_screen->_curPage, sx, sy, _screen->_curPage, sx, sy, 1, 6, 0, _mapOverlay); + + // draw west wall + drawMapBlockWall(b1, w13, sx, sy, 1); + drawMapShape(w13, sx, sy, 1); + if (_wllAutomapData[w13] & 0xC0) + _screen->copyBlockAndApplyOverlay(_screen->_curPage, sx + 6, sy, _screen->_curPage, sx + 6, sy, 1, 6, 0, _mapOverlay); + + // draw east wall + drawMapBlockWall(b0, w02, sx, sy, 0); + drawMapShape(w02, sx, sy, 0); + if (_wllAutomapData[w02] & 0xC0) + _screen->copyBlockAndApplyOverlay(_screen->_curPage, sx, sy, _screen->_curPage, sx, sy, 7, 1, 0, _mapOverlay); + + //draw south wall + drawMapBlockWall(b2, w20, sx, sy, 2); + drawMapShape(w20, sx, sy, 2); + if (_wllAutomapData[w20] & 0xC0) + _screen->copyBlockAndApplyOverlay(_screen->_curPage, sx, sy + 5, _screen->_curPage, sx, sy + 5, 7, 1, 0, _mapOverlay); + } + + sx += 7; + if (bl % 32 == 31) { + sx = _automapTopLeftX; + sy += 6; + bl += blX; + } + } + + _screen->setFont(of); + _screen->setCurPage(cp); + + of = _screen->setFont((_flags.lang == Common::JA_JPN && _flags.use16ColorMode) ? Screen::FID_SJIS_FNT : Screen::FID_6_FNT); + + int tY = 0; + sx = mapGetStartPosX(); + sy = mapGetStartPosY(); + + uint16 *legendData = (uint16 *)_tempBuffer5120; + uint8 yOffset = _flags.use16ColorMode ? 4 : 0; + + for (int ii = 0; ii < 32; ii++) { + uint16 *l = &legendData[ii * 6]; + if (l[0] == 0xFFFF) + break; + + uint16 cbl = l[0] + (l[1] << 5); + if ((_levelBlockProperties[cbl].flags & 7) != 7) + continue; + + if (l[2] == 0xFFFF) + continue; + + printMapText(l[2], 244 + xOffset, (tY << 3) + 22 + yOffset); + + if (l[5] == 0xFFFF) { + tY++; + continue; + } + + uint16 cbl2 = l[3] + (l[4] << 5); + _levelBlockProperties[cbl2].flags |= 7; + _screen->drawShape(2, _automapShapes[l[5] << 2], (l[3] - sx) * 7 + _automapTopLeftX - 3, (l[4] - sy) * 6 + _automapTopLeftY - 3, 0, 0); + _screen->drawShape(2, _automapShapes[l[5] << 2], 231 + xOffset, (tY << 3) + 19 + yOffset, 0, 0); + tY++; + } + + cp = _screen->setCurPage(pageNum); + + for (int ii = 0; ii < 11; ii++) { + if (!_defaultLegendData[ii].enable) + continue; + _screen->copyBlockAndApplyOverlay(_screen->_curPage, 235, (tY << 3) + 21 + yOffset, _screen->_curPage, 235 + xOffset, (tY << 3) + 21 + yOffset, 7, 6, 0, _mapOverlay); + _screen->drawShape(_screen->_curPage, _automapShapes[_defaultLegendData[ii].shapeIndex << 2], 232 + xOffset, (tY << 3) + 18 + yOffset + _defaultLegendData[ii].y, 0, 0); + printMapText(_defaultLegendData[ii].stringId, 244 + xOffset, (tY << 3) + 22 + yOffset); + tY++; + } + + _screen->setFont(of); + _screen->setCurPage(cp); + } + + printMapExitButtonText(); +} + +bool LoLEngine::automapProcessButtons(int inputFlag) { + int r = -1; + if (inputFlag == _keyMap[Common::KEYCODE_RIGHT] || inputFlag == _keyMap[Common::KEYCODE_KP6]) { + r = 0; + } else if (inputFlag == _keyMap[Common::KEYCODE_LEFT] || inputFlag == _keyMap[Common::KEYCODE_KP4]) { + r = 1; + } else if (inputFlag == 199) { + if (posWithinRect(_mouseX, _mouseY, 252, 175, 273, 200)) + r = 0; + else if (posWithinRect(_mouseX, _mouseY, 231, 175, 252, 200)) + r = 1; + else if (posWithinRect(_mouseX, _mouseY, 275, 175, 315, 197)) + r = 2; + + printMapExitButtonText(); + + while (inputFlag == 199 || inputFlag == 200) { + inputFlag = checkInput(0, false); + removeInputTop(); + delay(_tickLength); + } + } else { + return false; + } + + if (r == 0) { + automapForwardButton(); + printMapExitButtonText(); + } else if (r == 1) { + automapBackButton(); + printMapExitButtonText(); + } if (r == 2) { + return true; + } + + return false; +} + +void LoLEngine::automapForwardButton() { + int i = _currentMapLevel + 1; + while (!(_hasTempDataFlags & (1 << (i - 1)))) + i = (i + 1) & 0x1F; + if (i == _currentMapLevel) + return; + + for (int l = 0; l < 11; l++) + _defaultLegendData[l].enable = false; + + _currentMapLevel = i; + loadLevelWallData(i, false); + restoreBlockTempData(i); + loadMapLegendData(i); + _mapUpdateNeeded = true; +} + +void LoLEngine::automapBackButton() { + int i = _currentMapLevel - 1; + while (!(_hasTempDataFlags & (1 << (i - 1)))) + i = (i - 1) & 0x1F; + if (i == _currentMapLevel) + return; + + for (int l = 0; l < 11; l++) + _defaultLegendData[l].enable = false; + + _currentMapLevel = i; + loadLevelWallData(i, false); + restoreBlockTempData(i); + loadMapLegendData(i); + _mapUpdateNeeded = true; +} + +void LoLEngine::redrawMapCursor() { + int sx = mapGetStartPosX(); + int sy = mapGetStartPosY(); + + if (_currentLevel != _currentMapLevel) + return; + + int cx = _automapTopLeftX + (((_currentBlock - sx) % 32) * 7); + int cy = _automapTopLeftY + (((_currentBlock - (sy << 5)) / 32) * 6); + + if (_flags.use16ColorMode) { + _screen->drawShape(0, _automapShapes[48 + _currentDirection], cx - 3, cy - 2, 0, 0); + } else { + _screen->fillRect(0, 0, 16, 16, 0, 2); + _screen->drawShape(2, _automapShapes[48 + _currentDirection], 0, 0, 0, 0); + _screen->copyRegion(cx, cy, cx, cy, 16, 16, 2, 0); + _screen->copyBlockAndApplyOverlay(2, 0, 0, 0, cx - 3, cy - 2, 16, 16, 0, _mapCursorOverlay); + + _mapCursorOverlay[24] = _mapCursorOverlay[1]; + for (int i = 1; i < 24; i++) + _mapCursorOverlay[i] = _mapCursorOverlay[i + 1]; + } + + _screen->updateScreen(); +} + +void LoLEngine::drawMapBlockWall(uint16 block, uint8 wall, int x, int y, int direction) { + if (((1 << direction) & _levelBlockProperties[block].flags) || ((_wllAutomapData[wall] & 0x1F) != 13)) + return; + + int cp = _screen->_curPage; + _screen->copyBlockAndApplyOverlay(cp, x + _mapCoords[0][direction], y + _mapCoords[1][direction], cp, x + _mapCoords[0][direction], y + _mapCoords[1][direction], _mapCoords[2][direction], _mapCoords[3][direction], 0, _mapOverlay); + _screen->copyBlockAndApplyOverlay(cp, x + _mapCoords[4][direction], y + _mapCoords[5][direction], cp, x + _mapCoords[4][direction], y + _mapCoords[5][direction], _mapCoords[8][direction], _mapCoords[9][direction], 0, _mapOverlay); + _screen->copyBlockAndApplyOverlay(cp, x + _mapCoords[6][direction], y + _mapCoords[7][direction], cp, x + _mapCoords[6][direction], y + _mapCoords[7][direction], _mapCoords[8][direction], _mapCoords[9][direction], 0, _mapOverlay); +} + +void LoLEngine::drawMapShape(uint8 wall, int x, int y, int direction) { + int l = _wllAutomapData[wall] & 0x1F; + if (l == 0x1F) + return; + + _screen->drawShape(_screen->_curPage, _automapShapes[(l << 2) + direction], x + _mapCoords[10][direction] - 2, y + _mapCoords[11][direction] - 2, 0, 0); + mapIncludeLegendData(l); +} + +int LoLEngine::mapGetStartPosX() { + int c = 0; + int a = 32; + + do { + for (a = 0; a < 32; a++) { + if (_levelBlockProperties[(a << 5) + c].flags) + break; + } + if (a == 32) + c++; + } while (c < 32 && a == 32); + + int d = 31; + a = 32; + + do { + for (a = 0; a < 32; a++) { + if (_levelBlockProperties[(a << 5) + d].flags) + break; + } + if (a == 32) + d--; + } while (d > 0 && a == 32); + + _automapTopLeftX = (d > c) ? ((32 - (d - c)) >> 1) * 7 + 5 : 5; + return (d > c) ? c : 0; +} + +int LoLEngine::mapGetStartPosY() { + int c = 0; + int a = 32; + + do { + for (a = 0; a < 32; a++) { + if (_levelBlockProperties[(c << 5) + a].flags) + break; + } + if (a == 32) + c++; + } while (c < 32 && a == 32); + + int d = 31; + a = 32; + + do { + for (a = 0; a < 32; a++) { + if (_levelBlockProperties[(d << 5) + a].flags) + break; + } + if (a == 32) + d--; + } while (d > 0 && a == 32); + + _automapTopLeftY = (d > c) ? ((32 - (d - c)) >> 1) * 6 + 4 : 4; + return (d > c) ? c : 0; +} + +void LoLEngine::mapIncludeLegendData(int type) { + type &= 0x7F; + for (int i = 0; i < 11; i++) { + if (_defaultLegendData[i].shapeIndex != type) + continue; + _defaultLegendData[i].enable = true; + return; + } +} + +void LoLEngine::printMapText(uint16 stringId, int x, int y) { + int cp = _screen->setCurPage(2); + if (_flags.use16ColorMode) + _screen->printText(getLangString(stringId), x & ~3, y & ~7, 1, 0); + else + _screen->printText(getLangString(stringId), x, y, 239, 0); + _screen->setCurPage(cp); +} + +void LoLEngine::printMapExitButtonText() { + int cp = _screen->setCurPage(2); + Screen::FontId of = _screen->setFont(Screen::FID_9_FNT); + _screen->fprintString("%s", 295, 182, _flags.use16ColorMode ? 0xBB : 172, 0, 5, getLangString(0x4033)); + _screen->setFont(of); + _screen->setCurPage(cp); +} + + + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/lol.h b/engines/kyra/engine/lol.h new file mode 100644 index 0000000000..14811d21f1 --- /dev/null +++ b/engines/kyra/engine/lol.h @@ -0,0 +1,1345 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_LOL + +#ifndef KYRA_LOL_H +#define KYRA_LOL_H + +#include "kyra/engine/kyra_rpg.h" +#include "kyra/script/script_tim.h" +#include "kyra/script/script.h" +#include "kyra/gui/gui_lol.h" +#include "kyra/text/text_lol.h" + +#include "common/list.h" + +namespace Audio { +class SeekableAudioStream; +} // End of namespace Audio + +namespace Kyra { + +class Screen_LoL; +class WSAMovie_v2; +struct Button; + +struct LoLCharacter { + uint16 flags; + char name[11]; + uint8 raceClassSex; + int16 id; + uint8 curFaceFrame; + uint8 tempFaceFrame; + uint8 screamSfx; + const uint16 *defaultModifiers; + uint16 itemsMight[8]; + uint16 protectionAgainstItems[8]; + uint16 itemProtection; + int16 hitPointsCur; + uint16 hitPointsMax; + int16 magicPointsCur; + uint16 magicPointsMax; + uint8 field_41; + uint16 damageSuffered; + uint16 weaponHit; + uint16 totalMightModifier; + uint16 totalProtectionModifier; + uint16 might; + uint16 protection; + int16 nextAnimUpdateCountdown; + uint16 items[11]; + uint8 skillLevels[3]; + int8 skillModifiers[3]; + int32 experiencePts[3]; + uint8 characterUpdateEvents[5]; + uint8 characterUpdateDelay[5]; +}; + +struct SpellProperty { + uint16 spellNameCode; + uint16 mpRequired[4]; + uint16 field_a; + uint16 field_c; + uint16 hpRequired[4]; + uint16 field_16; + uint16 field_18; + uint16 flags; +}; + +struct LoLMonsterProperty { + uint8 shapeIndex; + uint8 maxWidth; + uint16 fightingStats[9]; + uint16 itemsMight[8]; + uint16 protectionAgainstItems[8]; + uint16 itemProtection; + uint16 hitPoints; + uint8 speedTotalWaitTicks; + uint8 skillLevel; + uint16 flags; + uint16 unk5; + uint16 numDistAttacks; + uint16 numDistWeapons; + uint16 distWeapons[3]; + uint8 attackSkillChance; + uint8 attackSkillType; + uint8 defenseSkillChance; + uint8 defenseSkillType; + uint8 sounds[3]; +}; + +struct LoLObject { + uint16 nextAssignedObject; + uint16 nextDrawObject; + uint8 flyingHeight; + uint16 block; + uint16 x; + uint16 y; +}; + +struct LoLMonster : public LoLObject { + uint8 destDirection; + int8 shiftStep; + uint16 destX; + uint16 destY; + + int8 hitOffsX; + int8 hitOffsY; + uint8 currentSubFrame; + uint8 mode; + int8 fightCurTick; + uint8 id; + uint8 direction; + uint8 facing; + uint16 flags; + uint16 damageReceived; + int16 hitPoints; + uint8 speedTick; + uint8 type; + LoLMonsterProperty *properties; + uint8 numDistAttacks; + uint8 curDistWeapon; + int8 distAttackTick; + uint16 assignedItems; + uint8 equipmentShapes[4]; +}; + +struct LoLItem : public LoLObject { + int8 level; + uint16 itemPropertyIndex; + uint16 shpCurFrame_flg; +}; + +struct ItemProperty { + uint16 nameStringId; + uint8 shpIndex; + uint16 flags; + uint16 type; + uint8 itemScriptFunc; + int8 might; + uint8 skill; + uint8 protection; + uint16 unkB; + uint8 unkD; +}; + +struct CompassDef { + uint8 shapeIndex; + int8 x; + int8 y; + uint8 flags; +}; + +struct LoLButtonDef { + uint16 buttonflags; + uint16 keyCode; + uint16 keyCode2; + int16 x; + int16 y; + uint16 w; + uint16 h; + uint16 index; + uint16 screenDim; +}; + +struct ActiveSpell { + uint8 spell; + const SpellProperty *p; + uint8 charNum; + uint8 level; + uint8 target; +}; + +struct FlyingObject { + uint8 enable; + uint8 objectType; + uint16 attackerId; + Item item; + uint16 x; + uint16 y; + uint8 flyingHeight; + uint8 direction; + uint8 distance; + int8 field_D; + uint8 c; + uint8 flags; + uint8 wallFlags; +}; + +struct FlyingObjectShape { + uint8 shapeFront; + uint8 shapeBack; + uint8 shapeLeft; + uint8 drawFlags; + uint8 flipFlags; +}; + +struct MapLegendData { + uint8 shapeIndex; + bool enable; + int8 y; + uint16 stringId; +}; + +struct LightningProperty { + uint8 lastFrame; + uint8 frameDiv; + int16 sfxId; +}; + +struct FireballState { + FireballState(int i) { + active = true; + destX = 200; + destY = 60; + tblIndex = ((i * 50) % 255) + 200; + progress = 1000; + step = 10; + finalize = false; + finProgress = 0; + } + + bool active; + int16 destX; + int16 destY; + uint16 tblIndex; + int32 progress; + uint8 step; + bool finalize; + uint8 finProgress; +}; + +struct MistOfDoomAnimData { + uint8 part1First; + uint8 part1Last; + uint8 part2First; + uint8 part2Last; + uint8 sound; +}; + +class LoLEngine : public KyraRpgEngine { +friend class GUI_LoL; +friend class TextDisplayer_LoL; +friend class TIMInterpreter_LoL; +friend class TimAnimator; +friend class Debugger_LoL; +friend class HistoryPlayer; +public: + LoLEngine(OSystem *system, const GameFlags &flags); + virtual ~LoLEngine(); + + virtual void initKeymap(); + + void pauseEngineIntern(bool pause); + + Screen *screen(); + GUI *gui() const; + +private: + Screen_LoL *_screen; + GUI_LoL *_gui; + + TIMInterpreter *_tim; + + Common::Error init(); + Common::Error go(); + + // initialization + void initStaticResource(); + void preInit(); + + void loadItemIconShapes(); + int mainMenu(); + + void startup(); + void startupNew(); + + void registerDefaultSettings(); + void writeSettings(); + void readSettings(); + + static const char *const kKeymapName; + + const char *const *_pakFileList; + int _pakFileListSize; + + // options + int _monsterDifficulty; + bool _smoothScrollingEnabled; + bool _floatingCursorsEnabled; + + // main loop + void runLoop(); + void update(); + + // mouse + void setMouseCursorToIcon(int icon); + void setMouseCursorToItemInHand(); + uint8 *getItemIconShapePtr(int index); + + void checkFloatingPointerRegions(); + int _floatingCursorControl; + int _currentFloatingCursor; + + // intro + character selection + int processPrologue(); + void setupPrologueData(bool load); + + void showIntro(); + + struct CharacterPrev { + int x, y; + int attrib[3]; + }; + + static const CharacterPrev _charPreviews[]; + static const char *const _charPreviewNamesDefault[]; + static const char *const _charPreviewNamesRussianFloppy[]; + + // PC98/FM-TOWNS specific data + static const uint16 _charPosXPC98[]; + static const char *const _charNamesJapanese[]; + + WSAMovie_v2 *_chargenWSA; + static const uint8 _chargenFrameTableTalkie[]; + static const uint8 _chargenFrameTableFloppy[]; + const uint8 *_chargenFrameTable; + int chooseCharacter(); + + void kingSelectionIntro(); + void kingSelectionReminder(); + void kingSelectionOutro(); + void processCharacterSelection(); + void updateSelectionAnims(); + int selectionCharInfo(int character); + void selectionCharInfoIntro(char *file); + + int getCharSelection(); + int selectionCharAccept(); + + void showStarcraftLogo(); + + int _charSelection; + int _charSelectionInfoResult; + + uint32 _selectionAnimTimers[4]; + uint8 _selectionAnimFrames[4]; + static const uint8 _selectionAnimIndexTable[]; + + static const uint16 _selectionPosTable[]; + + static const uint8 _selectionChar1IdxTable[]; + static const uint8 _selectionChar2IdxTable[]; + static const uint8 _selectionChar3IdxTable[]; + static const uint8 _selectionChar4IdxTable[]; + + static const uint8 _reminderChar1IdxTable[]; + static const uint8 _reminderChar2IdxTable[]; + static const uint8 _reminderChar3IdxTable[]; + static const uint8 _reminderChar4IdxTable[]; + + static const uint8 _charInfoFrameTable[]; + + // outro + void showOutro(int character, bool maxDifficulty); + void setupEpilogueData(bool load); + + void showCredits(); + void processCredits(char *text, int dimState, int page, int delay); + void loadOutroShapes(int file, uint8 **storage); + + uint8 _outroShapeTable[256]; + + // TODO: Consider moving these tables to kyra.dat + static const char *const _outroShapeFileTable[]; + static const uint8 _outroFrameTable[]; + + static const int16 _outroRightMonsterPos[]; + static const int16 _outroLeftMonsterPos[]; + static const int16 _outroRightDoorPos[]; + static const int16 _outroLeftDoorPos[]; + + static const int _outroMonsterScaleTableX[]; + static const int _outroMonsterScaleTableY[]; + + // Non-interactive demo + int playDemo(); + void pauseDemoPlayer(bool toggle); + + // timers + void setupTimers(); + + void timerProcessMonsters(int timerNum); + void timerSpecialCharacterUpdate(int timerNum); + void timerProcessFlyingObjects(int timerNum); + void timerRunSceneAnimScript(int timerNum); + void timerRegeneratePoints(int timerNum); + void timerUpdatePortraitAnimations(int skipUpdate); + void timerUpdateLampState(int timerNum); + void timerFadeMessageText(int timerNum); + + uint8 getClock2Timer(int index) { return index < _numClock2Timers ? _clock2Timers[index] : 0; } + uint8 getNumClock2Timers() { return _numClock2Timers; } + + static const uint8 _clock2Timers[]; + static const uint8 _numClock2Timers; + + // sound + int convertVolumeToMixer(int value); + int convertVolumeFromMixer(int value); + + void loadTalkFile(int index); + void snd_playVoiceFile(int track) {} + bool snd_playCharacterSpeech(int id, int8 speaker, int); + int snd_updateCharacterSpeech(); + void snd_stopSpeech(bool setFlag); + void snd_playSoundEffect(int track, int volume); + bool snd_processEnvironmentalSoundEffect(int soundId, int block); + void snd_queueEnvironmentalSoundEffect(int soundId, int block); + void snd_playQueuedEffects(); + void snd_loadSoundFile(int track); + int snd_playTrack(int track); + int snd_stopMusic(); + + int _lastSpeechId; + int _lastSpeaker; + int _lastSfxTrack; + int _lastMusicTrack; + int _curMusicFileIndex; + char _curMusicFileExt; + bool _envSfxUseQueue; + int _envSfxNumTracksInQueue; + uint16 _envSfxQueuedTracks[10]; + uint16 _envSfxQueuedBlocks[10]; + int _nextSpeechId; + int _nextSpeaker; + typedef Common::List SpeechList; + SpeechList _speechList; + + int _curTlkFile; + + char **_ingameSoundList; + int _ingameSoundListSize; + + const uint8 *_musicTrackMap; + const int16 *_ingameSoundIndex; + int _ingameSoundIndexSize; + const uint8 *_ingameGMSoundIndex; + int _ingameGMSoundIndexSize; + const uint8 *_ingameMT32SoundIndex; + int _ingameMT32SoundIndexSize; + const uint8 *_ingamePCSpeakerSoundIndex; + int _ingamePCSpeakerSoundIndexSize; + + // gui + void gui_drawPlayField(); + void gui_drawScene(int pageNum); + void gui_drawAllCharPortraitsWithStats(); + void gui_drawCharPortraitWithStats(int charNum); + void gui_drawCharFaceShape(int charNum, int x, int y, int pageNum); + void gui_highlightPortraitFrame(int charNum); + void gui_drawLiveMagicBar(int x, int y, int curPoints, int unk, int maxPoints, int w, int h, int col1, int col2, int flag); + void gui_drawMoneyBox(int pageNum); + void gui_drawInventory(); + void gui_drawInventoryItem(int index); + void gui_drawCompass(); + void gui_drawScroll(); + void gui_highlightSelectedSpell(bool mode); + void gui_displayCharInventory(int charNum); + void gui_printCharInventoryStats(int charNum); + void gui_printCharacterStats(int index, int redraw, int value); + void gui_changeCharacterStats(int charNum); + void gui_drawCharInventoryItem(int itemIndex); + + int gui_enableControls(); + int gui_disableControls(int controlMode); + void gui_toggleButtonDisplayMode(int shapeIndex, int mode); + void gui_toggleFightButtons(bool disable); + void gui_prepareForSequence(int x, int y, int w, int h, int buttonFlags); + void gui_specialSceneSuspendControls(int controlMode); + void gui_specialSceneRestoreControls(int restoreLamp); + + bool _weaponsDisabled; + int _lastButtonShape; + uint32 _buttonPressTimer; + int _selectedCharacter; + int _compassStep; + int _compassDirectionIndex; + uint32 _compassTimer; + int _charInventoryUnk; + + const CompassDef *_compassDefs; + + void gui_updateInput(); + void gui_triggerEvent(int eventType); + void gui_enableDefaultPlayfieldButtons(); + void gui_enableSequenceButtons(int x, int y, int w, int h, int enableFlags); + void gui_specialSceneRestoreButtons(); + void gui_enableCharInventoryButtons(int charNum); + + void gui_setFaceFramesControlButtons(int index, int xOffs); + void gui_initCharInventorySpecialButtons(int charNum); + void gui_initMagicScrollButtons(); + void gui_initMagicSubmenu(int charNum); + void gui_initButton(int index, int x = -1, int y = -1, int val = -1); + + LoLButtonDef _sceneWindowButton; + + int clickedUpArrow(Button *button); + int clickedDownArrow(Button *button); + int clickedLeftArrow(Button *button); + int clickedRightArrow(Button *button); + int clickedTurnLeftArrow(Button *button); + int clickedTurnRightArrow(Button *button); + int clickedAttackButton(Button *button); + int clickedMagicButton(Button *button); + int clickedMagicSubmenu(Button *button); + int clickedScreen(Button *button); + int clickedPortraitLeft(Button *button); + int clickedLiveMagicBarsLeft(Button *button); + int clickedPortraitEtcRight(Button *button); + int clickedCharInventorySlot(Button *button); + int clickedExitCharInventory(Button *button); + int clickedSceneDropItem(Button *button); + int clickedScenePickupItem(Button *button); + int clickedInventorySlot(Button *button); + int clickedInventoryScroll(Button *button); + int clickedWall(Button *button); + int clickedSequenceWindow(Button *button); + int clickedScroll(Button *button); + int clickedSpellTargetCharacter(Button *button); + int clickedSpellTargetScene(Button *button); + int clickedSceneThrowItem(Button *button); + int clickedOptions(Button *button); + int clickedRestParty(Button *button); + int clickedMoneyBox(Button *button); + int clickedCompass(Button *button); + int clickedAutomap(Button *button); + int clickedLamp(Button *button); + int clickedStatusIcon(Button *button); + + const LoLButtonDef *_buttonData; + const uint8 *_buttonList1; + const uint8 *_buttonList2; + const uint8 *_buttonList3; + const uint8 *_buttonList4; + const uint8 *_buttonList5; + const uint8 *_buttonList6; + const uint8 *_buttonList7; + const uint8 *_buttonList8; + + // text + int characterSays(int track, int charId, bool redraw); + int playCharacterScriptChat(int charId, int mode, int restorePortrait, char *str, EMCState *script, const uint16 *paramList, int16 paramIndex); + void setupDialogueButtons(int numStr, const char *s1, const char *s2, const char *s3); + + TextDisplayer_LoL *_txt; + TextDisplayer_rpg *txt() { return _txt; } + + // emc scripts + void runInitScript(const char *filename, int optionalFunc); + void runInfScript(const char *filename); + void runLevelScript(int block, int flags); + void runLevelScriptCustom(int block, int flags, int charNum, int item, int reg3, int reg4); + + EMCData _scriptData; + bool _suspendScript; + uint16 _scriptDirection; + int16 _globalScriptVars[24]; + + // emc opcode + int olol_setWallType(EMCState *script); + int olol_getWallType(EMCState *script); + int olol_drawScene(EMCState *script); + int olol_rollDice(EMCState *script); + int olol_moveParty(EMCState *script); + int olol_delay(EMCState *script); + int olol_setGameFlag(EMCState *script); + int olol_testGameFlag(EMCState *script); + int olol_loadLevelGraphics(EMCState *script); + int olol_loadBlockProperties(EMCState *script); + int olol_loadMonsterShapes(EMCState *script); + int olol_deleteHandItem(EMCState *script); + int olol_allocItemPropertiesBuffer(EMCState *script); + int olol_setItemProperty(EMCState *script); + int olol_makeItem(EMCState *script); + int olol_placeMoveLevelItem(EMCState *script); + int olol_createLevelItem(EMCState *script); + int olol_getItemPara(EMCState *script); + int olol_getCharacterStat(EMCState *script); + int olol_setCharacterStat(EMCState *script); + int olol_loadLevelShapes(EMCState *script); + int olol_closeLevelShapeFile(EMCState *script); + int olol_loadDoorShapes(EMCState *script); + int olol_initAnimStruct(EMCState *script); + int olol_playAnimationPart(EMCState *script); + int olol_freeAnimStruct(EMCState *script); + int olol_getDirection(EMCState *script); + int olol_characterSurpriseFeedback(EMCState *script); + int olol_setMusicTrack(EMCState *script); + int olol_setSequenceButtons(EMCState *script); + int olol_setDefaultButtonState(EMCState *script); + int olol_checkRectForMousePointer(EMCState *script); + int olol_clearDialogueField(EMCState *script); + int olol_setupBackgroundAnimationPart(EMCState *script); + int olol_startBackgroundAnimation(EMCState *script); + int olol_fadeToBlack(EMCState *script); + int olol_fadePalette(EMCState *script); + int olol_loadBitmap(EMCState *script); + int olol_stopBackgroundAnimation(EMCState *script); + int olol_getGlobalScriptVar(EMCState *script); + int olol_setGlobalScriptVar(EMCState *script); + int olol_getGlobalVar(EMCState *script); + int olol_setGlobalVar(EMCState *script); + int olol_triggerDoorSwitch(EMCState *script); + int olol_checkEquippedItemScriptFlags(EMCState *script); + int olol_setDoorState(EMCState *script); + int olol_updateBlockAnimations(EMCState *script); + int olol_assignLevelDecorationShape(EMCState *script); + int olol_resetBlockShapeAssignment(EMCState *script); + int olol_copyRegion(EMCState *script); + int olol_initMonster(EMCState *script); + int olol_fadeClearSceneWindow(EMCState *script); + int olol_fadeSequencePalette(EMCState *script); + int olol_redrawPlayfield(EMCState *script); + int olol_loadNewLevel(EMCState *script); + int olol_getNearestMonsterFromCharacter(EMCState *script); + int olol_dummy0(EMCState *script); + int olol_loadMonsterProperties(EMCState *script); + int olol_battleHitSkillTest(EMCState *script); + int olol_inflictDamage(EMCState *script); + int olol_moveMonster(EMCState *script); + int olol_setupDialogueButtons(EMCState *script); + int olol_giveTakeMoney(EMCState *script); + int olol_checkMoney(EMCState *script); + int olol_setScriptTimer(EMCState *script); + int olol_createHandItem(EMCState *script); + int olol_playAttackSound(EMCState *script); + int olol_addRemoveCharacter(EMCState *script); + int olol_giveItem(EMCState *script); + int olol_loadTimScript(EMCState *script); + int olol_runTimScript(EMCState *script); + int olol_releaseTimScript(EMCState *script); + int olol_initSceneWindowDialogue(EMCState *script); + int olol_restoreAfterSceneWindowDialogue(EMCState *script); + int olol_getItemInHand(EMCState *script); + int olol_checkMagic(EMCState *script); + int olol_giveItemToMonster(EMCState *script); + int olol_loadLangFile(EMCState *script); + int olol_playSoundEffect(EMCState *script); + int olol_processDialogue(EMCState *script); + int olol_stopTimScript(EMCState *script); + int olol_getWallFlags(EMCState *script); + int olol_changeMonsterStat(EMCState *script); + int olol_getMonsterStat(EMCState *script); + int olol_releaseMonsterShapes(EMCState *script); + int olol_playCharacterScriptChat(EMCState *script); + int olol_playEnvironmentalSfx(EMCState *script); + int olol_update(EMCState *script); + int olol_healCharacter(EMCState *script); + int olol_drawExitButton(EMCState *script); + int olol_loadSoundFile(EMCState *script); + int olol_playMusicTrack(EMCState *script); + int olol_deleteMonstersFromBlock(EMCState *script); + int olol_countBlockItems(EMCState *script); + int olol_characterSkillTest(EMCState *script); + int olol_countAllMonsters(EMCState *script); + int olol_playEndSequence(EMCState *script); + int olol_stopPortraitSpeechAnim(EMCState *script); + int olol_setPaletteBrightness(EMCState *script); + int olol_calcInflictableDamage(EMCState *script); + int olol_getInflictedDamage(EMCState *script); + int olol_checkForCertainPartyMember(EMCState *script); + int olol_printMessage(EMCState *script); + int olol_deleteLevelItem(EMCState *script); + int olol_calcInflictableDamagePerItem(EMCState *script); + int olol_distanceAttack(EMCState *script); + int olol_removeCharacterEffects(EMCState *script); + int olol_checkInventoryFull(EMCState *script); + int olol_moveBlockObjects(EMCState *script); + int olol_addSpellToScroll(EMCState *script); + int olol_playDialogueText(EMCState *script); + int olol_playDialogueTalkText(EMCState *script); + int olol_checkMonsterTypeHostility(EMCState *script); + int olol_setNextFunc(EMCState *script); + int olol_dummy1(EMCState *script); + int olol_suspendMonster(EMCState *script); + int olol_setScriptTextParameter(EMCState *script); + int olol_triggerEventOnMouseButtonClick(EMCState *script); + int olol_printWindowText(EMCState *script); + int olol_countSpecificMonsters(EMCState *script); + int olol_updateBlockAnimations2(EMCState *script); + int olol_checkPartyForItemType(EMCState *script); + int olol_blockDoor(EMCState *script); + int olol_resetTimDialogueState(EMCState *script); + int olol_getItemOnPos(EMCState *script); + int olol_removeLevelItem(EMCState *script); + int olol_savePage5(EMCState *script); + int olol_restorePage5(EMCState *script); + int olol_initDialogueSequence(EMCState *script); + int olol_restoreAfterDialogueSequence(EMCState *script); + int olol_setSpecialSceneButtons(EMCState *script); + int olol_restoreButtonsAfterSpecialScene(EMCState *script); + int olol_prepareSpecialScene(EMCState *script); + int olol_restoreAfterSpecialScene(EMCState *script); + int olol_assignCustomSfx(EMCState *script); + int olol_findAssignedMonster(EMCState *script); + int olol_checkBlockForMonster(EMCState *script); + int olol_crossFadeRegion(EMCState *script); + int olol_calcCoordinatesAddDirectionOffset(EMCState *script); + int olol_resetPortraitsAndDisableSysTimer(EMCState *script); + int olol_enableSysTimer(EMCState *script); + int olol_checkNeedSceneRestore(EMCState *script); + int olol_getNextActiveCharacter(EMCState *script); + int olol_paralyzePoisonCharacter(EMCState *script); + int olol_drawCharPortrait(EMCState *script); + int olol_removeInventoryItem(EMCState *script); + int olol_getAnimationLastPart(EMCState *script); + int olol_assignSpecialGuiShape(EMCState *script); + int olol_findInventoryItem(EMCState *script); + int olol_restoreFadePalette(EMCState *script); + int olol_getSelectedCharacter(EMCState *script); + int olol_setHandItem(EMCState *script); + int olol_drinkBezelCup(EMCState *script); + int olol_changeItemTypeOrFlag(EMCState *script); + int olol_placeInventoryItemInHand(EMCState *script); + int olol_castSpell(EMCState *script); + int olol_pitDrop(EMCState *script); + int olol_increaseSkill(EMCState *script); + int olol_paletteFlash(EMCState *script); + int olol_restoreMagicShroud(EMCState *script); + int olol_disableControls(EMCState *script); + int olol_enableControls(EMCState *script); + int olol_shakeScene(EMCState *script); + int olol_gasExplosion(EMCState *script); + int olol_calcNewBlockPosition(EMCState *script); + int olol_crossFadeScene(EMCState *script); + int olol_updateDrawPage2(EMCState *script); + int olol_setMouseCursor(EMCState *script); + int olol_characterSays(EMCState *script); + int olol_queueSpeech(EMCState *script); + int olol_getItemPrice(EMCState *script); + int olol_getLanguage(EMCState *script); + + // tim scripts + TIM *_activeTim[10]; + + // tim opcode + void setupOpcodeTable(); + + Common::Array _timIntroOpcodes; + int tlol_setupPaletteFade(const TIM *tim, const uint16 *param); + int tlol_loadPalette(const TIM *tim, const uint16 *param); + int tlol_setupPaletteFadeEx(const TIM *tim, const uint16 *param); + int tlol_processWsaFrame(const TIM *tim, const uint16 *param); + int tlol_displayText(const TIM *tim, const uint16 *param); + + Common::Array _timOutroOpcodes; + int tlol_fadeInScene(const TIM *tim, const uint16 *param); + int tlol_unusedResourceFunc(const TIM *tim, const uint16 *param); + int tlol_fadeInPalette(const TIM *tim, const uint16 *param); + int tlol_fadeSoundOut(const TIM *tim, const uint16 *param); + int tlol_displayAnimFrame(const TIM *tim, const uint16 *param); + int tlol_delayForChat(const TIM *tim, const uint16 *param); + int tlol_fadeOutSound(const TIM *tim, const uint16 *param); + + Common::Array _timIngameOpcodes; + int tlol_initSceneWindowDialogue(const TIM *tim, const uint16 *param); + int tlol_restoreAfterSceneWindowDialogue(const TIM *tim, const uint16 *param); + int tlol_giveItem(const TIM *tim, const uint16 *param); + int tlol_setPartyPosition(const TIM *tim, const uint16 *param); + int tlol_fadeClearWindow(const TIM *tim, const uint16 *param); + int tlol_copyRegion(const TIM *tim, const uint16 *param); + int tlol_characterChat(const TIM *tim, const uint16 *param); + int tlol_drawScene(const TIM *tim, const uint16 *param); + int tlol_update(const TIM *tim, const uint16 *param); + int tlol_clearTextField(const TIM *tim, const uint16 *param); + int tlol_loadSoundFile(const TIM *tim, const uint16 *param); + int tlol_playMusicTrack(const TIM *tim, const uint16 *param); + int tlol_playDialogueTalkText(const TIM *tim, const uint16 *param); + int tlol_playSoundEffect(const TIM *tim, const uint16 *param); + int tlol_startBackgroundAnimation(const TIM *tim, const uint16 *param); + int tlol_stopBackgroundAnimation(const TIM *tim, const uint16 *param); + + // translation + int _lang; + + uint8 *_landsFile; + uint8 *_levelLangFile; + + int _lastUsedStringBuffer; + char _stringBuffer[5][512]; // TODO: The original used a size of 512, it looks a bit large. + // Maybe we can someday reduce the size. + char *getLangString(uint16 id); + uint8 *getTableEntry(uint8 *buffer, uint16 id); + void decodeSjis(const char *src, char *dst); + int decodeCyrillic(const char *src, char *dst); + + static const char *const _languageExt[]; + + // graphics + void setupScreenDims(); + void initSceneWindowDialogue(int controlMode); + void restoreAfterSceneWindowDialogue(int redraw); + void initDialogueSequence(int controlMode, int pageNum); + void restoreAfterDialogueSequence(int controlMode); + void resetPortraitsAndDisableSysTimer(); + void toggleSelectedCharacterFrame(bool mode); + void fadeText(); + void transformRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage); + void setPaletteBrightness(const Palette &srcPal, int brightness, int modifier); + void generateBrightnessPalette(const Palette &src, Palette &dst, int brightness, int16 modifier); + void generateFlashPalette(const Palette &src, Palette &dst, int colorFlags); + void createTransparencyTables(); + void updateSequenceBackgroundAnimations(); + + uint8 **_itemIconShapes; + int _numItemIconShapes; + uint8 **_itemShapes; + int _numItemShapes; + uint8 **_gameShapes; + int _numGameShapes; + uint8 **_thrownShapes; + int _numThrownShapes; + uint8 **_effectShapes; + int _numEffectShapes; + + const int8 *_gameShapeMap; + + uint8 *_characterFaceShapes[40][3]; + + // characters + bool addCharacter(int id); + void setTemporaryFaceFrame(int charNum, int frame, int updateDelay, int redraw); + void setTemporaryFaceFrameForAllCharacters(int frame, int updateDelay, int redraw); + void setCharacterUpdateEvent(int charNum, int updateType, int updateDelay, int overwrite); + int countActiveCharacters(); + void loadCharFaceShapes(int charNum, int id); + void calcCharPortraitXpos(); + + void updatePortraitSpeechAnim(); + void stopPortraitSpeechAnim(); + void initTextFading(int textType, int clearField); + void setCharFaceFrame(int charNum, int frameNum); + void faceFrameRefresh(int charNum); + + void recalcCharacterStats(int charNum); + int calculateCharacterStats(int charNum, int index); + int calculateProtection(int index); + + void setCharacterMagicOrHitPoints(int charNum, int type, int points, int mode); + void increaseExperience(int charNum, int skill, uint32 points); + void increaseCharacterHitpoints(int charNum, int points, bool ignoreDeath); + + LoLCharacter *_characters; + uint16 _activeCharsXpos[3]; + + int _portraitSpeechAnimMode; + int _textColorFlag; + uint32 _palUpdateTimer; + uint32 _updatePortraitNext; + + int _loadLevelFlag; + int _activeMagicMenu; + uint16 _scriptCharacterCycle; + int _charStatsTemp[5]; + + const LoLCharacter *_charDefaults; + int _charDefaultsSize; + + const uint16 *_charDefsMan; + const uint16 *_charDefsWoman; + const uint16 *_charDefsKieran; + const uint16 *_charDefsAkshel; + const int32 *_expRequirements; + + // lamp + void resetLampStatus(); + void setLampMode(bool lampOn); + void updateLampStatus(); + + int8 _lampEffect; + int _brightness; + int _lampOilStatus; + uint32 _lampStatusTimer; + bool _lampStatusSuspended; + + // level + void loadLevel(int index); + void addLevelItems(); + void loadLevelWallData(int fileIndex, bool mapShapes); + void assignBlockItem(LevelBlockProperty *l, uint16 item); + int assignLevelDecorationShapes(int index); + uint8 *getLevelDecorationShapes(int index); + void releaseDecorations(int first = 0, int num = 400); + void restoreTempDataAdjustMonsterStrength(int index); + void loadBlockProperties(const char *cmzFile); + const uint8 *getBlockFileData(int levelIndex); + void loadLevelShpDat(const char *shpFile, const char *datFile, bool flag); + void loadLevelGraphics(const char *file, int specialColor, int weight, int vcnLen, int vmpLen, const char *palFile); + + void resetItems(int flag); + void disableMonsters(); + void resetBlockProperties(); + bool testWallFlag(int block, int direction, int flag); + bool testWallInvisibility(int block, int direction); + + void drawScene(int pageNum); + + void drawSceneShapes(int start = 0); + void drawDecorations(int index); + void drawBlockEffects(int index, int type); + void drawSpecialGuiShape(int pageNum); + void setWallType(int block, int wall, int val); + void updateDrawPage2(); + + void prepareSpecialScene(int fieldType, int hasDialogue, int suspendGui, int allowSceneUpdate, int controlMode, int fadeFlag); + int restoreAfterSpecialScene(int fadeFlag, int redrawPlayField, int releaseTimScripts, int sceneUpdateMode); + + void setSequenceButtons(int x, int y, int w, int h, int enableFlags); + void setSpecialSceneButtons(int x, int y, int w, int h, int enableFlags); + void setDefaultButtonState(); + + void updateCompass(); + + void moveParty(uint16 direction, int unk1, int unk2, int buttonShape); + void notifyBlockNotPassable(int scrollFlag); + virtual bool checkBlockPassability(uint16 block, uint16 direction); + + uint16 calcBlockIndex(uint16 x, uint16 y); + void calcCoordinates(uint16 &x, uint16 &y, int block, uint16 xOffs, uint16 yOffs); + void calcCoordinatesForSingleCharacter(int charNum, uint16 &x, uint16 &y); + void calcCoordinatesAddDirectionOffset(uint16 &x, uint16 &y, int direction); + + int clickedDoorSwitch(uint16 block, uint16 direction); + int clickedNiche(uint16 block, uint16 direction); + + void movePartySmoothScrollBlocked(int speed); + void movePartySmoothScrollUp(int speed); + void movePartySmoothScrollDown(int speed); + void movePartySmoothScrollLeft(int speed); + void movePartySmoothScrollRight(int speed); + void movePartySmoothScrollTurnLeft(int speed); + void movePartySmoothScrollTurnRight(int speed); + + void pitDropScroll(int numSteps); + + void shakeScene(int duration, int width, int height, int restore); + void processGasExplosion(int soundId); + + int smoothScrollDrawSpecialGuiShape(int pageNum); + + int _blockDoor; + + int _smoothScrollModeNormal; + + const uint8 *_scrollXTop; + const uint8 *_scrollYTop; + const uint8 *_scrollXBottom; + const uint8 *_scrollYBottom; + + int _nextScriptFunc; + int _lvlShapeIndex; + bool _partyAwake; + + uint8 *_specialGuiShape; + uint16 _specialGuiShapeX; + uint16 _specialGuiShapeY; + uint16 _specialGuiShapeMirrorFlag; + + Common::String _lastOverridePalFile; + int _lastSpecialColor; + int _lastSpecialColorWeight; + + uint8 *_transparencyTable2; + uint8 *_transparencyTable1; + + int _loadSuppFilesFlag; + uint8 *_wllAutomapData; + + uint16 _partyPosX; + uint16 _partyPosY; + + Common::SeekableReadStream *_lvlShpFileHandle; + + int _shpDmX; + int _shpDmY; + uint16 _dmScaleW; + uint16 _dmScaleH; + + int _lastMouseRegion; + int _seqWindowX1, _seqWindowY1, _seqWindowX2, _seqWindowY2, _seqTrigger; + int _spsWindowX, _spsWindowY, _spsWindowW, _spsWindowH; + + uint8 *_tempBuffer5120; + + const char *const *_levelDatList; + const char *const *_levelShpList; + + const int8 *_dscWalls; + + const uint8 *_dscOvlMap; + const uint8 *_dscShapeOvlIndex; + const uint16 *_dscShapeScaleW; + const uint16 *_dscShapeScaleH; + const int8 *_dscShapeY; + + const uint16 *_dscDoorMonsterScaleTable; + const uint16 *_dscDoor4; + const int16 *_dscDoorMonsterX; + const int16 *_dscDoorMonsterY; + + // objects (item/monster common) + LoLObject *findObject(uint16 index); + int calcObjectPosition(LoLObject *obj, uint16 direction); + void removeAssignedObjectFromBlock(LevelBlockProperty *l, uint16 id); + void removeDrawObjectFromBlock(LevelBlockProperty *l, uint16 id); + void assignObjectToBlock(uint16 *assignedBlockObjects, uint16 id); + + // items + void giveCredits(int credits, int redraw); + void takeCredits(int credits, int redraw); + Item makeItem(int itemType, int curFrame, int flags); + void placeMoveLevelItem(Item itemIndex, int level, int block, int xOffs, int yOffs, int flyingHeight); + bool addItemToInventory(Item itemIndex); + bool isItemMoveable(Item itemIndex); + void deleteItem(Item itemIndex); + void runItemScript(int charNum, Item item, int flags, int next, int reg4); + void setHandItem(Item itemIndex); + bool itemEquipped(int charNum, uint16 itemType); + + void setItemPosition(Item item, uint16 x, uint16 y, int flyingHeight, int moveable); + void removeLevelItem(Item item, int block); + bool launchObject(int objectType, Item item, int startX, int startY, int flyingHeight, int direction, int, int attackerId, int c); + void endObjectFlight(FlyingObject *t, int x, int y, int collisionType); + void processObjectFlight(FlyingObject *t, int x, int y); + void updateObjectFlightPosition(FlyingObject *t); + void objectFlightProcessHits(FlyingObject *t, int x, int y, int collisionType); + void updateFlyingObject(FlyingObject *t); + + void assignItemToBlock(uint16 *assignedBlockObjects, int id); + int checkDrawObjectSpace(int x1, int y1, int x2, int y2); + int checkSceneForItems(uint16 *blockDrawObjects, int color); + + uint8 _moneyColumnHeight[5]; + uint16 _credits; + + LoLItem *_itemsInPlay; + ItemProperty *_itemProperties; + + Item _itemInHand; + Item _inventory[48]; + Item _inventoryCurItem; + + int _lastCharInventory; + uint16 _charStatusFlags[3]; + int _emcLastItem; + + FlyingObject *_flyingObjects; + + EMCData _itemScript; + + const uint8 *_charInvIndex; + const uint8 *_charInvDefs; + const uint16 *_inventorySlotDesc; + const uint16 *_itemCost; + const uint8 *_stashSetupData; + const int8 *_sceneItemOffs; + const FlyingObjectShape *_flyingItemShapes; + + // monsters + void loadMonsterShapes(const char *file, int monsterIndex, int b); + void releaseMonsterShapes(int monsterIndex); + int deleteMonstersFromBlock(int block); + void setMonsterMode(LoLMonster *monster, int mode); + bool updateMonsterAdjustBlocks(LoLMonster *monster); + void placeMonster(LoLMonster *monster, uint16 x, uint16 y); + int calcMonsterDirection(uint16 x1, uint16 y1, uint16 x2, uint16 y2); + void setMonsterDirection(LoLMonster *monster, int dir); + void monsterDropItems(LoLMonster *monster); + void giveItemToMonster(LoLMonster *monster, Item item); + int checkBlockBeforeObjectPlacement(uint16 x, uint16 y, uint16 objectWidth, uint16 testFlag, uint16 wallFlag); + int testBlockPassability(int block, int x, int y, int objectWidth, int testFlag, int wallFlag); + int calcMonsterSkillLevel(int id, int a); + int checkBlockOccupiedByParty(int x, int y, int testFlag); + const uint16 *getCharacterOrMonsterStats(int id); + uint16 *getCharacterOrMonsterItemsMight(int id); + uint16 *getCharacterOrMonsterProtectionAgainstItems(int id); + + void drawBlockObjects(int blockArrayIndex); + void drawMonster(uint16 id); + int getMonsterCurFrame(LoLMonster *m, uint16 dirFlags); + void reassignDrawObjects(uint16 direction, uint16 itemIndex, LevelBlockProperty *l, bool flag); + void redrawSceneItem(); + void calcSpriteRelPosition(uint16 x1, uint16 y1, int &x2, int &y2, uint16 direction); + void drawDoor(uint8 *shape, uint8 *doorPalette, int index, int unk2, int w, int h, int flags); + void drawDoorOrMonsterEquipment(uint8 *shape, uint8 *objectPalette, int x, int y, int flags, const uint8 *brightnessOverlay); + uint8 *drawItemOrMonster(uint8 *shape, uint8 *monsterPalette, int x, int y, int fineX, int fineY, int flags, int tblValue, bool vflip); + int calcDrawingLayerParameters(int srcX, int srcY, int &x2, int &y2, uint16 &w, uint16 &h, uint8 *shape, int vflip); + + void updateMonster(LoLMonster *monster); + void moveMonster(LoLMonster *monster); + void walkMonster(LoLMonster *monster); + bool chasePartyWithDistanceAttacks(LoLMonster *monster); + void chasePartyWithCloseAttacks(LoLMonster *monster); + int walkMonsterCalcNextStep(LoLMonster *monster); + int checkForPossibleDistanceAttack(uint16 monsterBlock, int direction, int distance, uint16 curBlock); + int walkMonsterCheckDest(int x, int y, LoLMonster *monster, int unk); + void getNextStepCoords(int16 monsterX, int16 monsterY, int &newX, int &newY, uint16 direction); + void alignMonsterToParty(LoLMonster *monster); + void moveStrayingMonster(LoLMonster *monster); + void killMonster(LoLMonster *monster); + + LoLMonster *_monsters; + LoLMonsterProperty *_monsterProperties; + uint8 **_monsterDecorationShapes; + uint8 _monsterAnimType[3]; + uint16 _monsterCurBlock; + int _objectLastDirection; + + const uint16 *_monsterModifiers1; + const uint16 *_monsterModifiers2; + const uint16 *_monsterModifiers3; + const uint16 *_monsterModifiers4; + + const int8 *_monsterShiftOffs; + const uint8 *_monsterDirFlags; + const uint8 *_monsterScaleX; + const uint8 *_monsterScaleY; + const uint16 *_monsterScaleWH; + + // misc + void delay(uint32 millis, bool doUpdate = false, bool isMainLoop = false); + + const KyraRpgGUISettings *guiSettings(); + + uint8 _compassBroken; + uint8 _drainMagic; + uint16 _globalScriptVars2[8]; + + uint8 *_pageBuffer1; + uint8 *_pageBuffer2; + + static const KyraRpgGUISettings _guiSettings; + + // spells + typedef Common::Functor1Mem SpellProc; + Common::Array _spellProcs; + typedef void (LoLEngine::*SpellProcCallback)(WSAMovie_v2 *, int, int); + + int castSpell(int charNum, int spellType, int spellLevel); + + int castSpark(ActiveSpell *a); + int castHeal(ActiveSpell *a); + int castIce(ActiveSpell *a); + int castFireball(ActiveSpell *a); + int castHandOfFate(ActiveSpell *a); + int castMistOfDoom(ActiveSpell *a); + int castLightning(ActiveSpell *a); + int castFog(ActiveSpell *a); + int castSwarm(ActiveSpell *a); + int castVaelansCube(ActiveSpell *a); + int castGuardian(ActiveSpell *a); + int castHealOnSingleCharacter(ActiveSpell *a); + + int processMagicSpark(int charNum, int spellLevel); + int processMagicHealSelectTarget(); + int processMagicHeal(int charNum, int spellLevel); + int processMagicIce(int charNum, int spellLevel); + int processMagicFireball(int charNum, int spellLevel); + int processMagicHandOfFate(int spellLevel); + int processMagicMistOfDoom(int charNum, int spellLevel); + int processMagicLightning(int charNum, int spellLevel); + int processMagicFog(); + int processMagicSwarm(int charNum, int damage); + int processMagicVaelansCube(); + int processMagicGuardian(int charNum); + + void callbackProcessMagicSwarm(WSAMovie_v2 *mov, int x, int y); + void callbackProcessMagicLightning(WSAMovie_v2 *mov, int x, int y); + + void drinkBezelCup(int a, int charNum); + + void addSpellToScroll(int spell, int charNum); + void transferSpellToScollAnimation(int charNum, int spell, int slot); + + void playSpellAnimation(WSAMovie_v2 *mov, int firstFrame, int lastFrame, int frameDelay, int x, int y, SpellProcCallback callback, uint8 *pal1, uint8 *pal2, int fadeDelay, bool restoreScreen); + int checkMagic(int charNum, int spellNum, int spellLevel); + int getSpellTargetBlock(int currentBlock, int direction, int maxDistance, uint16 &targetBlock); + void inflictMagicalDamage(int target, int attacker, int damage, int index, int hitType); + void inflictMagicalDamageForBlock(int block, int attacker, int damage, int index); + + ActiveSpell _activeSpell; + int8 _availableSpells[8]; + int _selectedSpell; + const SpellProperty *_spellProperties; + //int _spellPropertiesSize; + int _subMenuIndex; + + LightningProperty *_lightningProps; + int16 _lightningCurSfx; + int16 _lightningDiv; + int16 _lightningFirstSfx; + int16 _lightningSfxFrame; + + uint8 *_healOverlay; + uint8 _swarmSpellStatus; + + uint8 **_fireballShapes; + int _numFireballShapes; + uint8 **_healShapes; + int _numHealShapes; + uint8 **_healiShapes; + int _numHealiShapes; + + static const MistOfDoomAnimData _mistAnimData[]; + + const uint8 *_updateSpellBookCoords; + const uint8 *_updateSpellBookAnimData; + const uint8 *_healShapeFrames; + const int16 *_fireBallCoords; + + // fight + int battleHitSkillTest(int16 attacker, int16 target, int skill); + int calcInflictableDamage(int16 attacker, int16 target, int hitType); + int inflictDamage(uint16 target, int damage, uint16 attacker, int skill, int flags); + void characterHitpointsZero(int16 charNum, int a); + void removeCharacterEffects(LoLCharacter *c, int first, int last); + int calcInflictableDamagePerItem(int16 attacker, int16 target, uint16 itemMight, int index, int hitType); + void checkForPartyDeath(); + + void applyMonsterAttackSkill(LoLMonster *monster, int16 target, int16 damage); + void applyMonsterDefenseSkill(LoLMonster *monster, int16 attacker, int flags, int skill, int damage); + int removeCharacterItem(int charNum, int itemFlags); + int paralyzePoisonCharacter(int charNum, int typeFlag, int immunityFlags, int hitChance, int redraw); + void paralyzePoisonAllCharacters(int typeFlag, int immunityFlags, int hitChance); + void stunCharacter(int charNum); + void restoreSwampPalette(); + + void launchMagicViper(); + + void breakIceWall(uint8 *pal1, uint8 *pal2); + + uint16 getNearestMonsterFromCharacter(int charNum); + uint16 getNearestMonsterFromCharacterForBlock(uint16 block, int charNum); + uint16 getNearestMonsterFromPos(int x, int y); + uint16 getNearestPartyMemberFromPos(int x, int y); + + int _partyDamageFlags; + + // magic atlas + void displayAutomap(); + void updateAutoMap(uint16 block); + bool updateAutoMapIntern(uint16 block, uint16 x, uint16 y, int16 xOffs, int16 yOffs); + void loadMapLegendData(int level); + void drawMapPage(int pageNum); + bool automapProcessButtons(int inputFlag); + void automapBackButton(); + void automapForwardButton(); + void redrawMapCursor(); + void drawMapBlockWall(uint16 block, uint8 wall, int x, int y, int direction); + void drawMapShape(uint8 wall, int x, int y, int direction); + int mapGetStartPosX(); + int mapGetStartPosY(); + void mapIncludeLegendData(int type); + void printMapText(uint16 stringId, int x, int y); + void printMapExitButtonText(); + + uint8 _currentMapLevel; + uint8 *_mapOverlay; + const uint8 **_automapShapes; + const uint16 *_autoMapStrings; + MapLegendData *_defaultLegendData; + uint8 *_mapCursorOverlay; + uint8 _automapTopLeftX; + uint8 _automapTopLeftY; + static const int8 _mapCoords[12][4]; + bool _mapUpdateNeeded; + + // unneeded + void setWalkspeed(uint8) {} + void removeHandItem() {} + bool lineIsPassable(int, int) { return false; } + + // save + Common::Error loadGameState(int slot); + Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail); + + void *generateMonsterTempData(LevelTempData *tmp); + void restoreBlockTempData(int levelIndex); + void restoreMonsterTempData(LevelTempData *tmp); + void releaseMonsterTempData(LevelTempData *tmp); + + Graphics::Surface *generateSaveThumbnail() const; +}; + +class HistoryPlayer { +public: + HistoryPlayer(LoLEngine *vm); + ~HistoryPlayer(); + + void play(); +private: + OSystem *_system; + LoLEngine *_vm; + Screen *_screen; + + int _x, _y, _width, _height; + int _frame; + Movie *_wsa; + + void loadWsa(const char *filename); + void playWsa(bool direction); + void restoreWsaBkgd(); + + Movie *_fireWsa; + int _fireFrame; + uint32 _nextFireTime; + void updateFire(); +}; + +} // End of namespace Kyra + +#endif + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/magic_eob.cpp b/engines/kyra/engine/magic_eob.cpp new file mode 100644 index 0000000000..d443b85c18 --- /dev/null +++ b/engines/kyra/engine/magic_eob.cpp @@ -0,0 +1,1381 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/resource/resource.h" +#include "common/system.h" + +namespace Kyra { + +void EoBCoreEngine::useMagicBookOrSymbol(int charIndex, int type) { + EoBCharacter *c = &_characters[charIndex]; + _openBookSpellLevel = c->slotStatus[3]; + _openBookSpellSelectedItem = c->slotStatus[2]; + _openBookSpellListOffset = c->slotStatus[4]; + _openBookChar = charIndex; + _openBookType = type; + _openBookSpellList = (type == 1) ? _clericSpellList : _mageSpellList; + _openBookAvailableSpells = (type == 1) ? c->clericSpells : c->mageSpells; + int8 *tmp = _openBookAvailableSpells + _openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem; + + if (*tmp <= 0) { + for (bool loop = true; loop && _openBookSpellSelectedItem < 10;) { + tmp = _openBookAvailableSpells + _openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem; + if (*tmp > 0) { + if (_openBookSpellSelectedItem > 5) { + _openBookSpellListOffset = 6; + _openBookSpellSelectedItem -= 6; + } + loop = false; + } else { + _openBookSpellSelectedItem++; + } + } + + if (_openBookSpellSelectedItem == 10) { + _openBookSpellListOffset = 0; + _openBookSpellSelectedItem = 6; + } + } + + if (!_updateFlags) + _screen->copyRegion(64, 121, 0, 0, 112, 56, 0, 10, Screen::CR_NO_P_CHECK); + _updateFlags = 1; + gui_setPlayFieldButtons(); + gui_drawSpellbook(); +} + +void EoBCoreEngine::useMagicScroll(int charIndex, int type, int weaponSlot) { + _openBookCharBackup = _openBookChar; + _openBookTypeBackup = _openBookType; + _castScrollSlot = weaponSlot + 1; + _openBookChar = charIndex; + _openBookType = type <= _clericSpellOffset ? 0 : 1; + castSpell(type, weaponSlot); +} + +void EoBCoreEngine::usePotion(int charIndex, int weaponSlot) { + EoBCharacter *c = &_characters[charIndex]; + + int val = deleteInventoryItem(charIndex, weaponSlot); + snd_playSoundEffect(10); + + if (_flags.gameID == GI_EOB1) + val--; + + switch (val) { + case 0: + sparkEffectDefensive(charIndex); + c->strengthCur = 22; + c->strengthExtCur = 0; + setCharEventTimer(charIndex, 546 * rollDice(1, 4, 4), 7, 1); + break; + + case 1: + sparkEffectDefensive(charIndex); + modifyCharacterHitpoints(charIndex, rollDice(2, 4, 2)); + break; + + case 2: + sparkEffectDefensive(charIndex); + modifyCharacterHitpoints(charIndex, rollDice(3, 8, 3)); + break; + + case 3: + statusAttack(charIndex, 2, _potionStrings[0], 0, 1, 8, 1); + c->effectFlags &= ~0x2000; + if (c->flags & 2) + return; + break; + + case 4: + sparkEffectDefensive(charIndex); + c->food = 100; + if (_currentControlMode) + gui_drawCharPortraitWithStats(charIndex); + break; + + case 5: + sparkEffectDefensive(charIndex); + c->effectFlags |= 0x10000; + setCharEventTimer(charIndex, 546 * rollDice(1, 4, 4), 12, 1); + snd_playSoundEffect(100); + gui_drawCharPortraitWithStats(charIndex); + break; + + case 6: + sparkEffectDefensive(charIndex); + c->effectFlags |= 0x40; + gui_drawCharPortraitWithStats(charIndex); + break; + + case 7: + sparkEffectDefensive(charIndex); + neutralizePoison(charIndex); + break; + + default: + break; + } + + _txt->printMessage(_potionStrings[1], -1, c->name, _potionEffectStrings[val]); +} + +void EoBCoreEngine::useWand(int charIndex, int weaponSlot) { + int v = _items[_characters[charIndex].inventory[weaponSlot]].value; + if (!v) { + _txt->printMessage(_wandStrings[0]); + return; + } + + if (v != 5) + useMagicScroll(charIndex, _wandTypes[v], weaponSlot); + else if (_flags.gameID == GI_EOB2) + useMagicScroll(charIndex, 64, weaponSlot); + else { + uint16 bl1 = calcNewBlockPosition(_currentBlock, _currentDirection); + uint16 bl2 = calcNewBlockPosition(bl1, _currentDirection); + snd_playSoundEffect(98); + sparkEffectOffensive(); + + if ((_wllWallFlags[_levelBlockProperties[bl2].walls[_currentDirection ^ 2]] & 4) && !(_levelBlockProperties[bl2].flags & 7) && (_levelBlockProperties[bl1].flags & 7)) { + for (int i = 0; i < 30; i++) { + if (_monsters[i].block != bl1) + continue; + placeMonster(&_monsters[i], bl2, -1); + _sceneUpdateRequired = true; + } + } else { + _txt->printMessage(_wandStrings[1]); + } + } +} + +void EoBCoreEngine::castSpell(int spell, int weaponSlot) { + EoBSpell *s = &_spells[spell]; + EoBCharacter *c = &_characters[_openBookChar]; + _activeSpell = spell; + + if ((s->flags & 0x100) && (c->effectFlags & 0x40)) + // remove invisibility effect + removeCharacterEffect(_flags.gameID == GI_EOB1 ? 8 : 10, _openBookChar, 1); + + int ci = _openBookChar; + if (ci > 3) + ci -= 2; + + _activeSpellCharacterPos = _dropItemDirIndex[(_currentDirection << 2) + ci]; + + if (s->flags & 0x400) { + if (c->inventory[0] && c->inventory[1]) { + printWarning(_magicStrings1[2]); + return; + } + + if (isMagicEffectItem(c->inventory[0]) || isMagicEffectItem(c->inventory[1])) { + printWarning(_magicStrings1[3]); + return; + } + } + + if (!(_flags.gameID == GI_EOB2 && _activeSpell == 62)) { + if (!_castScrollSlot) { + int8 tmp = _openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem]; + if (_openBookSpellListOffset + _openBookSpellSelectedItem < 8) + memmove(&_openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem], &_openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem + 1], 8 - (_openBookSpellListOffset + _openBookSpellSelectedItem)); + _openBookAvailableSpells[_openBookSpellLevel * 10 + 8] = -tmp; + if (_openBookAvailableSpells[_openBookSpellLevel * 10 + _openBookSpellListOffset + _openBookSpellSelectedItem] < 0) { + if (--_openBookSpellSelectedItem == -1) { + if (_openBookSpellListOffset) { + _openBookSpellListOffset = 0; + _openBookSpellSelectedItem = 5; + } else { + _openBookSpellSelectedItem = 6; + } + } + } + } else if (weaponSlot != -1) { + updateUsedCharacterHandItem(_openBookChar, weaponSlot); + } + } + + _txt->printMessage(_magicStrings1[4], -1, c->name, s->name); + + if (s->flags & 0x20) { + castOnWhomDialogue(); + return; + } + + _activeSpellCharId = _openBookChar; + startSpell(spell); +} + +void EoBCoreEngine::removeCharacterEffect(int spell, int charIndex, int showWarning) { + assert(spell >= 0); + EoBCharacter *c = &_characters[charIndex]; + EoBSpell *s = &_spells[spell]; + + if (showWarning) { + int od = _screen->curDimIndex(); + Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); + _screen->setScreenDim(7); + printWarning(Common::String::format(_magicStrings3[_flags.gameID == GI_EOB1 ? 3 : 2], c->name, s->name).c_str()); + _screen->setScreenDim(od); + _screen->setFont(of); + } + + if (s->endCallback) + (this->*s->endCallback)(c); + + if (s->flags & 1) + c->effectFlags &= ~s->effectFlags; + + if (s->flags & 4) + _partyEffectFlags &= ~s->effectFlags; + + if (s->flags & 0x200) { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (!testCharacter(i, 2) && !(s->flags & 0x800)) + continue; + _characters[i].effectFlags &= ~s->effectFlags; + } + } + + if (s->flags & 0x2) + recalcArmorClass(_activeSpellCharId); + + if (showWarning) { + if (s->flags & 0x20A0) + gui_drawCharPortraitWithStats(charIndex); + else if (s->flags & 0x40) + gui_drawAllCharPortraitsWithStats(); + } +} + +void EoBCoreEngine::removeAllCharacterEffects(int charIndex) { + EoBCharacter *c = &_characters[charIndex]; + c->effectFlags = 0; + memset(c->effectsRemainder, 0, 4); + + for (int i = 0; i < 10; i++) { + if (c->events[i] < 0) + removeCharacterEffect(-c->events[i], charIndex, 0); + c->timers[i] = 0; + c->events[i] = 0; + } + + setupCharacterTimers(); + recalcArmorClass(charIndex); + c->disabledSlots = 0; + c->slotStatus[0] = c->slotStatus[1] = 0; + c->damageTaken = 0; + c->strengthCur = c->strengthMax; + c->strengthExtCur = c->strengthExtMax; + gui_drawAllCharPortraitsWithStats(); +} + +void EoBCoreEngine::castOnWhomDialogue() { + printWarning(_magicStrings3[0]); + gui_setCastOnWhomButtons(); +} + +void EoBCoreEngine::startSpell(int spell) { + EoBSpell *s = &_spells[spell]; + EoBCharacter *c = &_characters[_activeSpellCharId]; + snd_playSoundEffect(s->sound); + + if (s->flags & 0xA0) + sparkEffectDefensive(_activeSpellCharId); + else if (s->flags & 0x40) + sparkEffectDefensive(-1); + else if (s->flags & 0x1000) + sparkEffectOffensive(); + + if (s->flags & 0x20) { + _txt->printMessage(c->name); + _txt->printMessage(_flags.gameID == GI_EOB1 ? _magicStrings3[1] : _magicStrings1[5]); + } + + if ((s->flags & 0x30) && (s->effectFlags & c->effectFlags)) { + if (_flags.gameID == GI_EOB2) + printWarning(Common::String::format(_magicStrings7[0], c->name, s->name).c_str()); + } else if ((s->flags & 0x50) && (s->effectFlags & _partyEffectFlags)) { + if (_flags.gameID == GI_EOB1 && s->effectFlags == 0x400) + // EOB 1 only warns in case of a bless spell + printWarning(_magicStrings8[1]); + else + printWarning(Common::String::format(_magicStrings7[1], s->name).c_str()); + } else { + if (s->flags & 8) + setSpellEventTimer(spell, s->timingPara[0], s->timingPara[1], s->timingPara[2], s->timingPara[3]); + + _returnAfterSpellCallback = false; + if (s->startCallback) + (this->*s->startCallback)(); + if (_returnAfterSpellCallback) + return; + + if (s->flags & 1) + c->effectFlags |= s->effectFlags; + if (s->flags & 4) + _partyEffectFlags |= s->effectFlags; + + if (s->flags & 0x200) { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (!testCharacter(i, 2) && !(s->flags & 0x800)) + continue; + _characters[i].effectFlags |= s->effectFlags; + } + } + + if (s->flags & 2) + recalcArmorClass(_activeSpellCharId); + + if (s->flags & 0x20A0) + gui_drawCharPortraitWithStats(_activeSpellCharId); + if (s->flags & 0x40) + gui_drawAllCharPortraitsWithStats(); + } + + if (_castScrollSlot) { + gui_updateSlotAfterScrollUse(); + } else { + _characters[_openBookChar].disabledSlots |= 4; + setCharEventTimer(_openBookChar, 72, 11, 1); + gui_toggleButtons(); + gui_drawSpellbook(); + } + + if (_flags.gameID == GI_EOB2) { + //_castSpellWd1 = spell; + runLevelScript(_currentBlock, 0x800); + //_castSpellWd1 = 0; + } +} + +void EoBCoreEngine::sparkEffectDefensive(int charIndex) { + int first = charIndex; + int last = charIndex; + if (charIndex == -1) { + first = 0; + last = 5; + } + + for (int i = 0; i < 8; i++) { + for (int ii = first; ii <= last; ii++) { + if (!testCharacter(ii, 1) || (_currentControlMode && ii != _updateCharNum)) + continue; + + gui_drawCharPortraitWithStats(ii); + + for (int iii = 0; iii < 4; iii++) { + int shpIndex = ((_sparkEffectDefSteps[i] & _sparkEffectDefSubSteps[iii]) >> _sparkEffectDefShift[iii]); + if (!shpIndex) + continue; + int x = _sparkEffectDefAdd[iii * 2] - 8; + int y = _sparkEffectDefAdd[iii * 2 + 1]; + if (_currentControlMode) { + x += 181; + y += 3; + } else { + x += (_sparkEffectDefX[ii] << 3); + y += _sparkEffectDefY[ii]; + } + _screen->drawShape(0, _sparkShapes[shpIndex - 1], x, y, 0); + _screen->updateScreen(); + } + } + delay(2 * _tickLength); + } + + for (int i = first; i < last; i++) + gui_drawCharPortraitWithStats(i); +} + +void EoBCoreEngine::sparkEffectOffensive() { + disableSysTimer(2); + _screen->copyRegion(0, 0, 0, 0, 176, 120, 0, 2, Screen::CR_NO_P_CHECK); + int sh = _flags.useHiColorMode ? 9 : 8; + + for (int i = 0; i < 16; i++) + _screen->copyRegionToBuffer(0, _sparkEffectOfX[i], _sparkEffectOfY[i], 16, 16, &_spellAnimBuffer[i << sh]); + + for (int i = 0; i < 11; i++) { + for (int ii = 0; ii < 16; ii++) + _screen->copyBlockToPage(2, _sparkEffectOfX[ii], _sparkEffectOfY[ii], 16, 16, &_spellAnimBuffer[ii << sh]); + + for (int ii = 0; ii < 16; ii++) { + int shpIndex = (_sparkEffectOfFlags1[i] & _sparkEffectOfFlags2[ii]) >> _sparkEffectOfShift[ii]; + if (shpIndex) + _screen->drawShape(2, _sparkShapes[shpIndex - 1], _sparkEffectOfX[ii], _sparkEffectOfY[ii], 0); + } + delay(2 * _tickLength); + _screen->copyRegion(0, 0, 0, 0, 176, 120, 2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + for (int i = 0; i < 16; i++) + _screen->copyBlockToPage(0, _sparkEffectOfX[i], _sparkEffectOfY[i], 16, 16, &_spellAnimBuffer[i << sh]); + + _screen->updateScreen(); + enableSysTimer(2); +} + +void EoBCoreEngine::setSpellEventTimer(int spell, int timerBaseFactor, int timerLength, int timerLevelFactor, int updateExistingTimer) { + assert(spell >= 0); + int l = _openBookType == 1 ? getClericPaladinLevel(_openBookChar) : getMageLevel(_openBookChar); + uint32 countdown = timerLength * timerBaseFactor + timerLength * l * timerLevelFactor; + setCharEventTimer(_activeSpellCharId, countdown, -spell, updateExistingTimer); +} + +void EoBCoreEngine::sortCharacterSpellList(int charIndex) { + int8 *list = _characters[charIndex].mageSpells; + + for (int i = 0; i < 16;) { + bool p = false; + for (int ii = 0; ii < 9; ii++) { + int8 *pos = &list[ii]; + + int s1 = pos[0]; + int s2 = pos[1]; + + if (s1 == 0) + s1 = 80; + else if (s1 < 0) + s1 = s1 * -1 + 40; + + if (s2 == 0) + s2 = 80; + else if (s2 < 0) + s2 = s2 * -1 + 40; + + if (s1 > s2) { + SWAP(pos[0], pos[1]); + p = true; + } + } + + if (p) + continue; + + list += 10; + if (++i == 8) + list = _characters[charIndex].clericSpells; + } +} + +bool EoBCoreEngine::magicObjectDamageHit(EoBFlyingObject *fo, int dcTimes, int dcPips, int dcOffs, int level) { + int ignoreAttackerId = fo->flags & 0x10; + int singleTargetCheckAdjacent = fo->flags & 1; + int blockDamage = fo->flags & 2; + int hitTest = fo->flags & 4; + + int savingThrowType = 5; + int savingThrowEffect = 3; + if (fo->flags & 8) { + savingThrowType = 4; + savingThrowEffect = 0; + } + + int dmgFlag = _spells[fo->callBackIndex].damageFlags; + if (fo->attackerId >= 0) + dmgFlag |= 0x800; + + bool res = false; + if (!level) + level = 1; + + if ((_levelBlockProperties[fo->curBlock].flags & 7) && (fo->attackerId >= 0 || ignoreAttackerId)) { + _preventMonsterFlash = true; + + for (const int16 *m = findBlockMonsters(fo->curBlock, fo->curPos, fo->direction, blockDamage, singleTargetCheckAdjacent); *m != -1; m++) { + int dmg = rollDice(dcTimes, dcPips, dcOffs) * level; + + if (hitTest) { + if (!characterAttackHitTest(fo->attackerId, *m, 0, 0)) + continue; + } + + calcAndInflictMonsterDamage(&_monsters[*m], 0, 0, dmg, dmgFlag, savingThrowType, savingThrowEffect); + res = true; + } + updateAllMonsterShapes(); + + } else if (fo->curBlock == _currentBlock && (fo->attackerId < 0 || ignoreAttackerId)) { + if (blockDamage) { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 1)) + continue; + if (hitTest && !monsterAttackHitTest(&_monsters[0], i)) + continue; + + int dmg = rollDice(dcTimes, dcPips, dcOffs) * level; + res = true; + + calcAndInflictCharacterDamage(i, 0, 0, dmg, dmgFlag, savingThrowType, savingThrowEffect); + } + } else { + int c = _dscItemPosIndex[(_currentDirection << 2) + (fo->curPos & 3)]; + if ((c > 2) && (testCharacter(4, 1) || testCharacter(5, 1)) && rollDice(1, 2, -1)) + c += 2; + + if (!fo->item && (_characters[c].effectFlags & 8)) { + res = true; + } else { + if ((_characters[c].flags & 1) && (!hitTest || monsterAttackHitTest(&_monsters[0], c))) { + int dmg = rollDice(dcTimes, dcPips, dcOffs) * level; + res = true; + calcAndInflictCharacterDamage(c, 0, 0, dmg, dmgFlag, savingThrowType, savingThrowEffect); + } + } + } + } + + if (res && (fo->flags & 0x40)) + explodeObject(fo, fo->curBlock, fo->item); + else if ((_flags.gameID == GI_EOB1 && fo->item == 5) || (_flags.gameID == GI_EOB2 && fo->item == 4)) + res = false; + + return res; +} + +bool EoBCoreEngine::magicObjectStatusHit(EoBMonsterInPlay *m, int type, bool tryEvade, int mod) { + EoBMonsterProperty *p = &_monsterProps[m->type]; + if (tryEvade) { + if (tryMonsterAttackEvasion(m) || (p->immunityFlags & 0x10)) + return true; + } + + if (trySavingThrow(m, 0, p->level, mod, 6)) + return false; + + int para = 0; + + switch (type) { + case 0: + case 1: + case 2: + para = (type == 0) ? ((p->typeFlags & 1) ? 1 : 0) : ((type == 1) ? ((p->typeFlags & 2) ? 1 : 0) : 1); + if (para && !(p->immunityFlags & 2)) { + m->mode = 10; + m->spellStatusLeft = 15; + } + + break; + + case 3: + if (!(p->immunityFlags & 8)) + inflictMonsterDamage(m, 1000, true); + break; + + case 4: + inflictMonsterDamage(m, 1000, true); + break; + + case 5: + m->flags |= 0x20; + _sceneUpdateRequired = true; + break; + + case 6: + if (!(_flags.gameID == GI_EOB1 && !(p->typeFlags & 3)) && !(p->immunityFlags & 4) && m->mode != 7 && m->mode != 8 && m->mode != 10) { + m->mode = 0; + m->spellStatusLeft = 20; + m->flags |= 8; + walkMonsterNextStep(m, -1, (getNextMonsterDirection(m->block, _currentBlock) ^ 4) >> 1); + } + break; + + default: + break; + } + + return true; +} + +bool EoBCoreEngine::turnUndeadHit(EoBMonsterInPlay *m, int hitChance, int casterLevel) { + assert(_monsterProps[m->type].tuResist > 0); + uint8 e = _turnUndeadEffect[_monsterProps[m->type].tuResist * 14 + MIN(casterLevel, 14)]; + + if (e == 0xFF) { + calcAndInflictMonsterDamage(m, 0, 0, 500, 0x200, 5, 3); + } else if (hitChance < e) { + return false; + } else { + m->mode = 0; + m->flags |= 8; + m->spellStatusLeft = 40; + m->dir = (getNextMonsterDirection(m->block, _currentBlock) ^ 4) >> 1; + } + + return true; +} + +int EoBCoreEngine::getMagicWeaponSlot(int charIndex) { + return _characters[charIndex].inventory[1] ? 0 : 1; +} + +void EoBCoreEngine::causeWounds(int dcTimes, int dcPips, int dcOffs) { + if (_openBookChar == 0 || _openBookChar == 1) { + int d = getClosestMonster(_openBookChar, calcNewBlockPosition(_currentBlock, _currentDirection)); + if (d != -1) { + if (!characterAttackHitTest(_openBookChar, d, 0, 1)) + return; + + if (dcTimes == -1) { + dcOffs = _monsters[d].hitPointsMax - rollDice(1, 4); + dcTimes = dcPips = 0; + } + calcAndInflictMonsterDamage(&_monsters[d], dcTimes, dcPips, dcOffs, 0x801, 4, 2); + } else { + printWarning(Common::String::format(_magicStrings3[_flags.gameID == GI_EOB1 ? 4 : 3], _characters[_openBookChar].name).c_str()); + } + } else { + printWarning(Common::String::format(_magicStrings3[_flags.gameID == GI_EOB1 ? 5 : 4], _characters[_openBookChar].name).c_str()); + } +} + +int EoBCoreEngine::createMagicWeaponType(int invFlags, int handFlags, int armorClass, int allowedClasses, int dmgNumDice, int dmgPips, int dmgInc, int extraProps) { + int i = 51; + for (; i < 57; i++) { + if (_itemTypes[i].armorClass == -30) + break; + } + + if (i == 57) + return -1; + + EoBItemType *tp = &_itemTypes[i]; + tp->invFlags = invFlags; + tp->requiredHands = 0; + tp->handFlags = handFlags; + tp->armorClass = armorClass; + tp->allowedClasses = allowedClasses; + tp->dmgNumDiceL = tp->dmgNumDiceS = dmgNumDice; + tp->dmgNumPipsL = tp->dmgNumPipsS = dmgPips; + tp->dmgIncL = tp->dmgIncS = dmgInc; + tp->extraProperties = extraProps; + + return i; +} + +Item EoBCoreEngine::createMagicWeaponItem(int flags, int icon, int value, int type) { + Item i = 11; + for (; i < 17; i++) { + if (_items[i].block == -2) + break; + } + + if (i == 17) + return -1; + + EoBItem *itm = &_items[i]; + itm->flags = 0x20 | flags; + itm->icon = icon; + itm->value = value; + itm->type = type; + itm->pos = 0; + itm->block = 0; + itm->nameId = itm->nameUnid = 0; + itm->prev = itm->next = 0; + + return i; +} + +void EoBCoreEngine::removeMagicWeaponItem(Item item) { + _itemTypes[_items[item].type].armorClass = -30; + _items[item].block = -2; + _items[item].level = 0xFF; +} + +void EoBCoreEngine::updateWallOfForceTimers() { + uint32 ct = _system->getMillis(); + for (int i = 0; i < 5; i++) { + if (!_wallsOfForce[i].block) + continue; + if (_wallsOfForce[i].duration < ct) + destroyWallOfForce(i); + } +} + +void EoBCoreEngine::destroyWallOfForce(int index) { + memset(_levelBlockProperties[_wallsOfForce[index].block].walls, 0, 4); + _wallsOfForce[index].block = 0; + _sceneUpdateRequired = true; +} + +int EoBCoreEngine::findSingleSpellTarget(int dist) { + uint16 bl = _currentBlock; + int res = -1; + + for (int i = 0; i < dist && res == -1; i++) { + bl = calcNewBlockPosition(bl, _currentDirection); + res = getClosestMonster(_openBookChar, bl); + if (!(_wllWallFlags[_levelBlockProperties[bl].walls[_sceneDrawVarDown]] & 1)) { + i = dist; + res = -1; + } + } + + return res; +} + +int EoBCoreEngine::findFirstCharacterSpellTarget() { + int curCharIndex = rollDice(1, 6, -1); + for (_characterSpellTarget = 0; _characterSpellTarget < 6; _characterSpellTarget++) { + if (testCharacter(curCharIndex, 3)) + return curCharIndex; + if (++curCharIndex == 6) + curCharIndex = 0; + } + return -1; +} + +int EoBCoreEngine::findNextCharacterSpellTarget(int curCharIndex) { + for (_characterSpellTarget++; _characterSpellTarget < 6;) { + if (++curCharIndex == 6) + curCharIndex = 0; + if (testCharacter(curCharIndex, 3)) + return curCharIndex; + } + return -1; +} + +int EoBCoreEngine::charDeathSavingThrow(int charIndex, int div) { + bool _beholderOrgBhv = true; + // Due to a bug in the original code the saving throw result is completely ignored + // here. The Beholders' disintegrate spell will alway succeed while their flesh to + // stone spell will always fail. + if (_beholderOrgBhv) + div >>= 1; + else + div = specialAttackSavingThrow(charIndex, 4) ? 1 : 0; + return div; +} + +void EoBCoreEngine::printWarning(const char *str) { + _txt->printMessage(str); + snd_playSoundEffect(79); +} + +void EoBCoreEngine::printNoEffectWarning() { + printWarning(_magicStrings4[0]); +} + +void EoBCoreEngine::spellCallback_start_armor() { + _characters[_activeSpellCharId].effectsRemainder[0] = getMageLevel(_openBookChar) + 8; + if ((getDexterityArmorClassModifier(_characters[_activeSpellCharId].dexterityCur) + 6) >= _characters[_activeSpellCharId].armorClass) + printWarning(Common::String::format(_magicStrings6[0], _characters[_activeSpellCharId].name).c_str()); +} + +void EoBCoreEngine::spellCallback_start_burningHands() { + static const int16 bX[] = { 0, 152, 24, 120, 56, 88 }; + static const int8 bY[] = { 64, 64, 56, 56, 56, 56 }; + + for (int i = 0; i < 6; i++) + drawBlockObject(i & 1, 0, _firebeamShapes[(5 - i) >> 1], bX[i], bY[i], 0); + _screen->updateScreen(); + delay(2 * _tickLength); + + int cl = getMageLevel(_openBookChar); + int bl = calcNewBlockPosition(_currentBlock, _currentDirection); + + const int8 *pos = getMonstersOnBlockPositions(bl); + _preventMonsterFlash = true; + + int numDest = (_flags.gameID == GI_EOB1) ? 2 : 6; + const uint8 *d = &_burningHandsDest[_currentDirection * (_flags.gameID == GI_EOB1 ? 2 : 8)]; + + for (int i = 0; i < numDest; i++, d++) { + if (pos[*d] == -1) + continue; + calcAndInflictMonsterDamage(&_monsters[pos[*d]], 1, 3, cl << 1, 0x21, 4, 0); + } + + updateAllMonsterShapes(); + _sceneUpdateRequired = true; +} + +void EoBCoreEngine::spellCallback_start_detectMagic() { + setHandItem(_itemInHand); +} + +bool EoBCoreEngine::spellCallback_end_detectMagic(void *) { + setHandItem(_itemInHand); + return true; +} + +void EoBCoreEngine::spellCallback_start_magicMissile() { + launchMagicObject(_openBookChar, 0, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_magicMissile(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 1, 4, 1, (getMageLevel(fo->attackerId) - 1) >> 1); +} + +void EoBCoreEngine::spellCallback_start_shockingGrasp() { + int t = createMagicWeaponType(0, 0, 0, 0x0F, 1, 8, getMageLevel(_openBookChar), 1); + Item i = (t != -1) ? createMagicWeaponItem(0x10, 82, 0, t) : -1; + if (t == -1 || i == -1) { + if (_flags.gameID == GI_EOB2) + printWarning(_magicStrings8[0]); + removeCharacterEffect(_activeSpell, _activeSpellCharId, 0); + deleteCharEventTimer(_activeSpellCharId, -_activeSpell); + _returnAfterSpellCallback = true; + } else { + _characters[_activeSpellCharId].inventory[getMagicWeaponSlot(_activeSpellCharId)] = i; + } +} + +bool EoBCoreEngine::spellCallback_end_shockingGraspFlameBlade(void *obj) { + EoBCharacter *c = (EoBCharacter *)obj; + for (int i = 0; i < 2; i++) { + if (isMagicEffectItem(c->inventory[i])) { + removeMagicWeaponItem(c->inventory[i]); + c->inventory[i] = 0; + } + } + return true; +} + +void EoBCoreEngine::spellCallback_start_improvedIdentify() { + for (int i = 0; i < 2; i++) { + Item itm = _characters[_activeSpellCharId].inventory[i]; + if (itm) + _items[itm].flags |= 0x40; + } +} + +void EoBCoreEngine::spellCallback_start_melfsAcidArrow() { + launchMagicObject(_openBookChar, 1, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_melfsAcidArrow(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + assert(fo); + return magicObjectDamageHit(fo, 2, 4, 0, getMageLevel(fo->attackerId) / 3); +} + +void EoBCoreEngine::spellCallback_start_dispelMagic() { + for (int i = 0; i < 6; i++) { + if (testCharacter(i, 1)) + removeAllCharacterEffects(i); + } +} + +void EoBCoreEngine::spellCallback_start_fireball() { + launchMagicObject(_openBookChar, 2, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_fireball(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId)); +} + +void EoBCoreEngine::spellCallback_start_flameArrow() { + launchMagicObject(_openBookChar, 3, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_flameArrow(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 5, 6, 0, getMageLevel(fo->attackerId)); +} + +void EoBCoreEngine::spellCallback_start_holdPerson() { + launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 4 : 3, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_holdPerson(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + bool res = false; + + if (_flags.gameID == GI_EOB2 && fo->curBlock == _currentBlock) { + // party hit + int numChar = rollDice(1, 4, 0); + int charIndex = rollDice(1, 6, -1); + for (int i = 0; i < 6 && numChar; i++) { + if (testCharacter(charIndex, 3)) { + statusAttack(charIndex, 4, _magicStrings8[1], 4, 5, 9, 1); + numChar--; + } + charIndex = (charIndex + 1) % 6; + } + res = true; + + } else { + // monster hit + for (const int16 *m = findBlockMonsters(fo->curBlock, fo->curPos, fo->direction, 1, 1); *m != -1; m++) + res |= magicObjectStatusHit(&_monsters[*m], 0, true, 4); + } + + return res; +} + +void EoBCoreEngine::spellCallback_start_lightningBolt() { + launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 5 : 4, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_lightningBolt(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId)); +} + +void EoBCoreEngine::spellCallback_start_vampiricTouch() { + int t = createMagicWeaponType(0, 0, 0, 0x0F, getMageLevel(_openBookChar) >> 1, 6, 0, 1); + Item i = (t != -1) ? createMagicWeaponItem(0x18, 83, 0, t) : -1; + if (t == -1 || i == -1) { + if (_flags.gameID == GI_EOB2) + printWarning(_magicStrings8[2]); + removeCharacterEffect(_activeSpell, _activeSpellCharId, 0); + deleteCharEventTimer(_activeSpellCharId, -_activeSpell); + _returnAfterSpellCallback = true; + } else { + _characters[_activeSpellCharId].inventory[getMagicWeaponSlot(_activeSpellCharId)] = i; + } +} + +bool EoBCoreEngine::spellCallback_end_vampiricTouch(void *obj) { + EoBCharacter *c = (EoBCharacter *)obj; + if (c->hitPointsCur > c->hitPointsMax) + c->hitPointsCur = c->hitPointsMax; + spellCallback_end_shockingGraspFlameBlade(obj); + return true; +} + +void EoBCoreEngine::spellCallback_start_fear() { + sparkEffectOffensive(); + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + for (int i = 0; i < 30; i++) { + if (_monsters[i].block == bl) + magicObjectStatusHit(&_monsters[i], 6, true, 4); + } +} + +void EoBCoreEngine::spellCallback_start_iceStorm() { + launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 6 : 5, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_iceStorm(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + static int8 blockAdv[] = { -32, 32, 1, -1 }; + bool res = magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId)); + if (res) { + for (int i = 0; i < 4; i++) { + uint16 bl = fo->curBlock; + fo->curBlock = (fo->curBlock + blockAdv[i]) & 0x3FF; + magicObjectDamageHit(fo, 1, 6, 0, getMageLevel(fo->attackerId)); + fo->curBlock = bl; + } + } + return res; +} + +void EoBCoreEngine::spellCallback_start_stoneSkin() { + _characters[_activeSpellCharId].effectsRemainder[1] = (getMageLevel(_openBookChar) >> 1) + rollDice(1, 4); +} + +void EoBCoreEngine::spellCallback_start_removeCurse() { + for (int i = 0; i < 27; i++) { + Item itm = _characters[_activeSpellCharId].inventory[i]; + if (itm && (_items[itm].flags & 0x20) && !isMagicEffectItem(itm)) + _items[itm].flags = (_items[itm].flags & ~0x20) | 0x40; + } +} + +void EoBCoreEngine::spellCallback_start_coneOfCold() { + const int8 *dirTables[] = { _coneOfColdDest1, _coneOfColdDest2, _coneOfColdDest3, _coneOfColdDest4 }; + + int cl = getMageLevel(_openBookChar); + + _screen->setCurPage(2); + _screen->fillRect(0, 0, 176, 120, 0); + _screen->setGfxParameters(0, 0, _screen->getPagePixel(2, 0, 0)); + drawSceneShapes(7); + _screen->setCurPage(0); + disableSysTimer(2); + _screen->drawVortex(150, 50, 10, 1, 100, _coneOfColdGfxTbl, _coneOfColdGfxTblSize); + enableSysTimer(2); + + const int8 *tbl = dirTables[_currentDirection]; + _preventMonsterFlash = true; + + for (int i = 0; i < 7; i++) { + for (const int16 *m = findBlockMonsters((_currentBlock + tbl[i]) & 0x3FF, 4, _currentDirection, 1, 1); *m != -1; m++) + calcAndInflictMonsterDamage(&_monsters[*m], cl, 4, cl, 0x41, 5, 0); + } + + updateAllMonsterShapes(); +} + +void EoBCoreEngine::spellCallback_start_holdMonster() { + launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 7 : 6, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_holdMonster(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + bool res = false; + for (const int16 *m = findBlockMonsters(fo->curBlock, fo->curPos, fo->direction, 1, 1); *m != -1; m++) + res |= magicObjectStatusHit(&_monsters[*m], 1, true, 4); + return res; +} + +void EoBCoreEngine::spellCallback_start_wallOfForce() { + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + LevelBlockProperty *l = &_levelBlockProperties[bl]; + if (l->walls[0] || l->walls[1] || l->walls[2] || l->walls[3] || (l->flags & 7)) { + printWarning(_magicStrings8[3]); + return; + } + + uint32 dur = 0xFFFFFFFF; + int s = 0; + int i = 0; + + for (; i < 5; i++) { + if (!_wallsOfForce[i].block) + break; + if (_wallsOfForce[i].duration < dur) { + dur = _wallsOfForce[i].duration; + s = i; + } + } + + if (i == 5) + destroyWallOfForce(s); + + memset(_levelBlockProperties[bl].walls, 74, 4); + _wallsOfForce[s].block = bl; + _wallsOfForce[s].duration = _system->getMillis() + (((getMageLevel(_openBookChar) * 546) >> 1) + 546) * _tickLength; + _sceneUpdateRequired = true; +} + +void EoBCoreEngine::spellCallback_start_disintegrate() { + int d = findSingleSpellTarget(1); + if (d != -1) + magicObjectStatusHit(&_monsters[d], 4, true, 4); + memset(_visibleBlocks[13]->walls, 0, 4); + _sceneUpdateRequired = true; +} + +void EoBCoreEngine::spellCallback_start_fleshToStone() { + sparkEffectOffensive(); + int t = getClosestMonster(_openBookChar, calcNewBlockPosition(_currentBlock, _currentDirection)); + if (t != -1) + magicObjectStatusHit(&_monsters[t], 5, true, 4); + else + printWarning(_magicStrings8[4]); +} + +void EoBCoreEngine::spellCallback_start_stoneToFlesh() { + if (_characters[_activeSpellCharId].flags & 8) + _characters[_activeSpellCharId].flags &= ~8; + else + printNoEffectWarning(); +} + +void EoBCoreEngine::spellCallback_start_trueSeeing() { + _wllVmpMap[46] = 0; +} + +bool EoBCoreEngine::spellCallback_end_trueSeeing(void *) { + _wllVmpMap[46] = 1; + return true; +} + +void EoBCoreEngine::spellCallback_start_slayLiving() { + int d = findSingleSpellTarget(2); + if (d != -1) { + if (!magicObjectStatusHit(&_monsters[d], 3, true, 4)) + inflictMonsterDamage(&_monsters[d], rollDice(2, 8, 1), true); + } +} + +void EoBCoreEngine::spellCallback_start_powerWordStun() { + int d = findSingleSpellTarget(2); + if (d != -1) { + if (_monsters[d].hitPointsCur < 90) + magicObjectStatusHit(&_monsters[d], 5, true, 4); + } +} + +void EoBCoreEngine::spellCallback_start_causeLightWounds() { + causeWounds(1, 8, 0); +} + +void EoBCoreEngine::spellCallback_start_cureLightWounds() { + modifyCharacterHitpoints(_activeSpellCharId, rollDice(1, 8)); +} + +void EoBCoreEngine::spellCallback_start_aid() { + if (!testCharacter(_activeSpellCharId, 3)) { + printNoEffectWarning(); + } else if (_characters[_activeSpellCharId].effectsRemainder[3]) { + printWarning(Common::String::format(_magicStrings8[(_flags.gameID == GI_EOB1) ? 2 : 5], _characters[_activeSpellCharId].name).c_str()); + } else { + _characters[_activeSpellCharId].effectsRemainder[3] = rollDice(1, 8); + _characters[_activeSpellCharId].hitPointsCur += _characters[_activeSpellCharId].effectsRemainder[3]; + _characters[_activeSpellCharId].effectFlags |= 0x1000; + return; + } + + removeCharacterEffect(_activeSpell, _activeSpellCharId, 0); + deleteCharEventTimer(_activeSpellCharId, -_activeSpell); +} + +bool EoBCoreEngine::spellCallback_end_aid(void *obj) { + EoBCharacter *c = (EoBCharacter *)obj; + c->hitPointsCur -= c->effectsRemainder[3]; + c->effectsRemainder[3] = 0; + c->effectFlags &= ~0x1000; + return true; +} + +void EoBCoreEngine::spellCallback_start_flameBlade() { + int t = createMagicWeaponType(0, 0, 0, 0x0F, 1, 4, 4, 1); + Item i = (t != -1) ? createMagicWeaponItem(0, 84, 0, t) : -1; + if (t == -1 || i == -1) { + if (_flags.gameID == GI_EOB2) + printWarning(_magicStrings8[0]); + removeCharacterEffect(_activeSpell, _activeSpellCharId, 0); + deleteCharEventTimer(_activeSpellCharId, -_activeSpell); + _returnAfterSpellCallback = true; + } else { + _characters[_activeSpellCharId].inventory[getMagicWeaponSlot(_activeSpellCharId)] = i; + } +} + +void EoBCoreEngine::spellCallback_start_slowPoison() { + if (_characters[_activeSpellCharId].flags & 2) { + _characters[_activeSpellCharId].effectFlags |= 0x2000; + setSpellEventTimer(_activeSpell, 1, 32760, 1, 1); + } else { + printNoEffectWarning(); + } +} + +bool EoBCoreEngine::spellCallback_end_slowPoison(void *obj) { + EoBCharacter *c = (EoBCharacter *)obj; + c->effectFlags &= ~0x2000; + return true; +} + +void EoBCoreEngine::spellCallback_start_createFood() { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 3)) + continue; + _characters[i].food = 100; + } +} + +void EoBCoreEngine::spellCallback_start_removeParalysis() { + int numChar = 4; + for (int i = 0; i < 6; i++) { + if (!(_characters[i].flags & 4) || !numChar) + continue; + _characters[i].flags &= ~4; + numChar--; + } +} + +void EoBCoreEngine::spellCallback_start_causeSeriousWounds() { + causeWounds(2, 8, 1); +} + +void EoBCoreEngine::spellCallback_start_cureSeriousWounds() { + modifyCharacterHitpoints(_activeSpellCharId, rollDice(2, 8, 1)); +} + +void EoBCoreEngine::spellCallback_start_neutralizePoison() { + if (_characters[_activeSpellCharId].flags & 2) + neutralizePoison(_activeSpellCharId); + else + printNoEffectWarning(); +} + +void EoBCoreEngine::spellCallback_start_causeCriticalWounds() { + causeWounds(3, 8, 3); +} + +void EoBCoreEngine::spellCallback_start_cureCriticalWounds() { + modifyCharacterHitpoints(_activeSpellCharId, rollDice(3, 8, 3)); +} + +void EoBCoreEngine::spellCallback_start_flameStrike() { + launchMagicObject(_openBookChar, _flags.gameID == GI_EOB1 ? 8 : 7, _currentBlock, _activeSpellCharacterPos, _currentDirection); +} + +bool EoBCoreEngine::spellCallback_end_flameStrike(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 6, 8, 0, 0); +} + +void EoBCoreEngine::spellCallback_start_raiseDead() { + if (_characters[_activeSpellCharId].hitPointsCur == -10 && ((_characters[_activeSpellCharId].raceSex >> 1) != 1)) { + _characters[_activeSpellCharId].hitPointsCur = 1; + gui_drawCharPortraitWithStats(_activeSpellCharId); + } else { + printNoEffectWarning(); + } +} + +void EoBCoreEngine::spellCallback_start_harm() { + causeWounds(-1, -1, -1); +} + +void EoBCoreEngine::spellCallback_start_heal() { + EoBCharacter *c = &_characters[_activeSpellCharId]; + if (c->hitPointsMax <= c->hitPointsCur) + printWarning(_magicStrings4[0]); + else + modifyCharacterHitpoints(_activeSpellCharId, c->hitPointsMax - c->hitPointsCur); +} + +void EoBCoreEngine::spellCallback_start_layOnHands() { + modifyCharacterHitpoints(_activeSpellCharId, _characters[_openBookChar].level[0] << 1); +} + +void EoBCoreEngine::spellCallback_start_turnUndead() { + uint16 bl = calcNewBlockPosition(_currentBlock, _currentDirection); + if (!(_levelBlockProperties[bl].flags & 7)) + return; + + int cl = _openBookCasterLevel ? _openBookCasterLevel : getClericPaladinLevel(_openBookChar); + int r = rollDice(1, 20); + bool hit = false; + + for (const int16 *m = findBlockMonsters(bl, 4, 4, 1, 1); *m != -1; m++) { + if ((_monsterProps[_monsters[*m].type].typeFlags & 4) && !(_monsters[*m].flags & 0x10)) { + _preventMonsterFlash = true; + _monsters[*m].flags |= 0x10; + hit |= turnUndeadHit(&_monsters[*m], r, cl); + } + } + + if (hit) { + turnUndeadAutoHit(); + snd_playSoundEffect(95); + updateAllMonsterShapes(); + } + + _preventMonsterFlash = false; +} + +bool EoBCoreEngine::spellCallback_end_monster_lightningBolt(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 0, 0, 12, 1); +} + +bool EoBCoreEngine::spellCallback_end_monster_fireball1(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + bool res = false; + if (_partyEffectFlags & 0x20000) { + res = magicObjectDamageHit(fo, 4, 10, 6, 0); + if (res) { + gui_drawAllCharPortraitsWithStats(); + _partyEffectFlags &= ~0x20000; + } + } else { + res = magicObjectDamageHit(fo, 12, 10, 6, 0); + } + return res; +} + +bool EoBCoreEngine::spellCallback_end_monster_fireball2(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + return magicObjectDamageHit(fo, 0, 0, 18, 0); +} + +bool EoBCoreEngine::spellCallback_end_monster_deathSpell(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + if (fo->curBlock != _currentBlock) + return false; + + int numDest = rollDice(1, 4); + _txt->printMessage(_magicStrings2[2]); + for (int d = findFirstCharacterSpellTarget(); d != -1 && numDest; d = findNextCharacterSpellTarget(d)) { + if (_characters[d].level[0] < 8) { + inflictCharacterDamage(d, 300); + numDest--; + } + } + + return true; +} + +bool EoBCoreEngine::spellCallback_end_monster_disintegrate(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + if (fo->curBlock != _currentBlock) + return false; + + int d = findFirstCharacterSpellTarget(); + if (d != -1) { + if (!charDeathSavingThrow(d, 1)) { + inflictCharacterDamage(d, 300); + _txt->printMessage(_magicStrings2[1], -1, _characters[d].name); + } + } + + return true; +} + +bool EoBCoreEngine::spellCallback_end_monster_causeCriticalWounds(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + if (fo->curBlock != _currentBlock) + return false; + + int d = findFirstCharacterSpellTarget(); + if (d != -1) { + _txt->printMessage(_magicStrings2[3], -1, _characters[d].name); + inflictCharacterDamage(d, rollDice(3, 8, 3)); + } + + return true; +} + +bool EoBCoreEngine::spellCallback_end_monster_fleshToStone(void *obj) { + EoBFlyingObject *fo = (EoBFlyingObject *)obj; + if (fo->curBlock != _currentBlock) + return false; + + int d = findFirstCharacterSpellTarget(); + while (d != -1) { + if (!charDeathSavingThrow(d, 2)) { + statusAttack(d, 8, _magicStrings2[4], 5, 0, 0, 1); + d = -1; + } else { + d = findNextCharacterSpellTarget(d); + } + } + + return true; +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/scene_eob.cpp b/engines/kyra/engine/scene_eob.cpp new file mode 100644 index 0000000000..3ff26cab8a --- /dev/null +++ b/engines/kyra/engine/scene_eob.cpp @@ -0,0 +1,862 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/resource/resource.h" +#include "kyra/script/script_eob.h" +#include "kyra/engine/timer.h" +#include "kyra/sound/sound.h" + +#include "common/system.h" + + +namespace Kyra { + +void EoBCoreEngine::loadLevel(int level, int sub) { + _currentLevel = level; + _currentSub = sub; + if (!_loading) + setHandItem(-1); + uint32 end = _system->getMillis() + 500; + + readLevelFileData(level); + + Common::String gfxFile; + // Work around for issue with corrupt (incomplete) monster property data + // when loading a savegame saved in a sub level + for (int i = 0; i <= sub; i++) + gfxFile = initLevelData(i); + + const uint8 *data = _screen->getCPagePtr(5); + const uint8 *pos = data + READ_LE_UINT16(data); + uint16 len = READ_LE_UINT16(pos); + uint16 len2 = len; + pos += 2; + + if (_flags.gameID == GI_EOB2) { + if (*pos++ == 0xEC) + pos = loadActiveMonsterData(pos, level); + else if (!(_hasTempDataFlags & (1 << (level - 1)))) + memset(_monsters, 0, 30 * sizeof(EoBMonsterInPlay)); + + len2 = len - (pos - data); + _inf->loadData(pos, len2); + } else { + _inf->loadData(data, READ_LE_UINT16(data)); + } + + _screen->setCurPage(2); + addLevelItems(); + + if (_flags.gameID == GI_EOB2) { + pos = data + len; + len2 = READ_LE_UINT16(pos); + pos += 2; + } + + for (uint16 i = 0; i < len2; i++) { + LevelBlockProperty *p = &_levelBlockProperties[READ_LE_UINT16(pos)]; + pos += 2; + if (_flags.gameID == GI_EOB2) { + p->flags |= READ_LE_UINT16(pos); + pos += 2; + } else { + p->flags |= *pos++; + } + p->assignedObjects = READ_LE_UINT16(pos); + pos += 2; + } + + // WORKAROUND for bug #3596547 (EOB1: Door Buttons Don't Work) + if (_flags.gameID == GI_EOB1 && level == 7 && _levelBlockProperties[0x035C].assignedObjects == 0x0E89) + _levelBlockProperties[0x035C].assignedObjects = 0x0E8D; + + loadVcnData(gfxFile.c_str(), (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[level - 1]] : 0); + _screen->loadEoBBitmap("INVENT", _cgaMappingInv, 5, 3, 2); + delayUntil(end); + snd_stopSound(); + + enableSysTimer(2); + _sceneDrawPage1 = 2; + _sceneDrawPage2 = 1; + _screen->setCurPage(0); + setHandItem(_itemInHand); +} + +void EoBCoreEngine::readLevelFileData(int level) { + Common::String file; + Common::SeekableReadStream *s = 0; + static const char *const suffix[] = { "INF", "DRO", "ELO", "JOT", 0 }; + + for (const char *const *sf = suffix; *sf && !s; sf++) { + file = Common::String::format("LEVEL%d.%s", level, *sf); + s = _res->createReadStream(file); + } + + if (!s) + error("Failed to load level file LEVEL%d.INF/DRO/ELO/JOT", level); + + if (s->readUint16LE() + 2 == s->size()) { + if (s->readUint16LE() == 4) { + delete s; + s = 0; + _screen->loadBitmap(file.c_str(), 5, 5, 0); + } + } + + if (s) { + s->seek(0); + _screen->loadFileDataToPage(s, 5, 15000); + delete s; + } +} + +Common::String EoBCoreEngine::initLevelData(int sub) { + const uint8 *data = _screen->getCPagePtr(5) + 2; + const uint8 *pos = data; + + int slen = (_flags.gameID == GI_EOB1) ? 12 : 13; + + for (int i = 0; i < sub; i++) + pos = data + READ_LE_UINT16(pos); + + pos += 2; + if (*pos++ == 0xEC || _flags.gameID == GI_EOB1) { + if (_flags.gameID == GI_EOB1) + pos -= 3; + + loadBlockProperties((const char *)pos); + pos += slen; + + const char *vmpPattern = (_flags.gameID == GI_EOB1 && (_configRenderMode == Common::kRenderEGA || _configRenderMode == Common::kRenderCGA)) ? "%s.EMP" : "%s.VMP"; + Common::SeekableReadStream *s = _res->createReadStream(Common::String::format(vmpPattern, (const char *)pos)); + uint16 size = (_flags.platform == Common::kPlatformFMTowns) ? 2916 : s->readUint16LE(); + delete[] _vmpPtr; + _vmpPtr = new uint16[size]; + for (int i = 0; i < size; i++) + _vmpPtr[i] = s->readUint16LE(); + delete s; + + const char *paletteFilePattern = (_flags.gameID == GI_EOB2 && _configRenderMode == Common::kRenderEGA) ? "%s.EGA" : "%s.PAL"; + + Common::String tmpStr = Common::String::format(paletteFilePattern, (const char *)pos); + _curGfxFile = (const char *)pos; + pos += slen; + + if (*pos++ != 0xFF && _flags.gameID == GI_EOB2) { + tmpStr = Common::String::format(paletteFilePattern, (const char *)pos); + pos += 13; + } + + if (_flags.gameID == GI_EOB1) { + pos += 11; + _screen->setShapeFadingLevel(0); + _screen->enableShapeBackgroundFading(false); + } + + if (_flags.gameID == GI_EOB2 || _configRenderMode != Common::kRenderEGA) + _screen->loadPalette(tmpStr.c_str(), _screen->getPalette(0)); + + if (_flags.platform == Common::kPlatformFMTowns) { + uint16 *src = (uint16*)_screen->getPalette(0).getData(); + _screen->createFadeTable16bit(src, (uint16*)_greenFadingTable, 4, 75); + _screen->createFadeTable16bit(src, (uint16*)_blackFadingTable, 12, 200); + _screen->createFadeTable16bit(src, (uint16*)_blueFadingTable, 10, 85); + _screen->createFadeTable16bit(src, (uint16*)_lightBlueFadingTable, 11, 125); + _screen->createFadeTable16bit(src, (uint16*)_greyFadingTable, 0, 85); + _screen->setScreenPalette(_screen->getPalette(0)); + } else if (_configRenderMode != Common::kRenderCGA) { + Palette backupPal(256); + backupPal.copy(_screen->getPalette(0), 224, 32, 224); + _screen->getPalette(0).fill(224, 32, 0x3F); + uint8 *src = _screen->getPalette(0).getData(); + + _screen->createFadeTable(src, _greenFadingTable, 4, 75); + _screen->createFadeTable(src, _blackFadingTable, 12, 200); + _screen->createFadeTable(src, _blueFadingTable, 10, 85); + _screen->createFadeTable(src, _lightBlueFadingTable, 11, 125); + + _screen->getPalette(0).copy(backupPal, 224, 32, 224); + _screen->createFadeTable(src, _greyFadingTable, 12, 85); + _screen->setFadeTable(_greyFadingTable); + if (_flags.gameID == GI_EOB2 && _configRenderMode == Common::kRenderEGA) + _screen->setScreenPalette(_screen->getPalette(0)); + } + } + + if (_flags.gameID == GI_EOB2) { + delay(3 * _tickLength); + _sound->loadSoundFile((const char *)pos); + pos += 13; + } + + releaseDoorShapes(); + releaseMonsterShapes(0, 36); + releaseDecorations(); + + if (_flags.gameID == GI_EOB1) { + loadDoorShapes(pos[0], pos[1], pos[2], pos[3]); + pos += 4; + _scriptTimersMode = *pos++; + _scriptTimers[0].ticks = READ_LE_UINT16(pos); + _scriptTimers[0].func = 0; + _scriptTimers[0].next = _system->getMillis() + _scriptTimers[0].ticks * _tickLength; + pos += 2; + } else { + for (int i = 0; i < 2; i++) { + int a = (*pos == 0xEC) ? 0 : ((*pos == 0xEA) ? 1 : -1); + pos++; + if (a == -1) + continue; + toggleWallState(pos[13], a); + _doorType[pos[13]] = pos[14]; + _noDoorSwitch[pos[13]] = pos[15]; + pos = loadDoorShapes((const char *)pos, pos[13], pos + 16); + } + } + + _stepsUntilScriptCall = READ_LE_UINT16(pos); + pos += 2; + _stepCounter = 0; + + for (int i = 0; i < 2; i++) { + if (_flags.gameID == GI_EOB1) { + if (*pos != 0xFF) + loadMonsterShapes((const char *)(pos + 1), i * 18, false, *pos * 18); + pos += 13; + } else { + if (*pos++ != 0xEC) + continue; + loadMonsterShapes((const char *)(pos + 2), pos[1] * 18, pos[15] ? true : false, *pos * 18); + pos += 16; + } + } + + if (_flags.gameID == GI_EOB1) + pos = loadActiveMonsterData(pos, _currentLevel) - 1; + else + pos = loadMonsterProperties(pos); + + if (*pos++ == 0xEC || _flags.gameID == GI_EOB1) { + int num = READ_LE_UINT16(pos); + pos += 2; + + for (int i = 0; i < num; i++) { + if (*pos++ == 0xEC) { + loadDecorations((const char *)pos, (const char *)(pos + slen)); + pos += (slen << 1); + } else { + assignWallsAndDecorations(pos[0], pos[1], (int8)pos[2], pos[3], pos[4]); + pos += 5; + } + } + } + + if (_flags.gameID == GI_EOB2) + initScriptTimers(pos); + + return _curGfxFile; +} + +void EoBCoreEngine::addLevelItems() { + for (int i = 0; i < 1024; i++) + _levelBlockProperties[i].drawObjects = 0; + + for (int i = 0; i < 600; i++) { + if (_items[i].level != _currentLevel || _items[i].block <= 0) + continue; + setItemPosition((Item *)&_levelBlockProperties[_items[i].block & 0x3FF].drawObjects, _items[i].block, i, _items[i].pos); + } +} + +void EoBCoreEngine::loadVcnData(const char *file, const uint8 *cgaMapping) { + if (file) + strcpy(_lastBlockDataFile, file); + + if (_flags.platform == Common::kPlatformFMTowns) { + uint32 size; + delete[] _vcnBlocks; + _vcnBlocks = _res->fileData(Common::String::format("%s.VCC", _lastBlockDataFile).c_str(), &size); + return; + } + + const char *filePattern = ((_flags.gameID == GI_EOB1 && (_configRenderMode == Common::kRenderEGA || _configRenderMode == Common::kRenderCGA)) ? "%s.ECN" : "%s.VCN"); + _screen->loadBitmap(Common::String::format(filePattern, _lastBlockDataFile).c_str(), 3, 3, 0); + const uint8 *pos = _screen->getCPagePtr(3); + + uint32 vcnSize = READ_LE_UINT16(pos) << 5; + pos += 2; + + const uint8 *colMap = pos; + pos += 32; + + delete[] _vcnBlocks; + _vcnBlocks = new uint8[vcnSize]; + + if (_configRenderMode == Common::kRenderCGA) { + uint8 *tmp = _screen->encodeShape(0, 0, 1, 8, false, cgaMapping); + delete[] tmp; + + delete[] _vcnTransitionMask; + _vcnTransitionMask = new uint8[vcnSize]; + uint8 tblSwitch = 1; + uint8 *dst = _vcnBlocks; + uint8 *dst2 = _vcnTransitionMask; + + while (dst < _vcnBlocks + vcnSize) { + const uint16 *table = _screen->getCGADitheringTable((tblSwitch++) & 1); + for (int ii = 0; ii < 2; ii++) { + *dst++ = (table[pos[0]] & 0x000F) | ((table[pos[0]] & 0x0F00) >> 4); + *dst++ = (table[pos[1]] & 0x000F) | ((table[pos[1]] & 0x0F00) >> 4); + *dst2++ = ((pos[0] & 0xF0 ? 0x30 : 0) | (pos[0] & 0x0F ? 0x03 : 0)) ^ 0x33; + *dst2++ = ((pos[1] & 0xF0 ? 0x30 : 0) | (pos[1] & 0x0F ? 0x03 : 0)) ^ 0x33; + pos += 2; + } + } + } else { + if (!(_flags.gameID == GI_EOB1 && _configRenderMode == Common::kRenderEGA)) + memcpy(_vcnColTable, colMap, 32); + memcpy(_vcnBlocks, pos, vcnSize); + } +} + +void EoBCoreEngine::loadBlockProperties(const char *mazFile) { + memset(_levelBlockProperties, 0, 1024 * sizeof(LevelBlockProperty)); + const uint8 *p = getBlockFileData(mazFile) + 6; + + if (_hasTempDataFlags & (1 << (_currentLevel - 1))) { + restoreBlockTempData(_currentLevel); + return; + } + + for (int i = 0; i < 1024; i++) { + for (int ii = 0; ii < 4; ii++) + _levelBlockProperties[i].walls[ii] = *p++; + } +} + +const uint8 *EoBCoreEngine::getBlockFileData(int) { + Common::SeekableReadStream *s = _res->createReadStream(_curBlockFile); + _screen->loadFileDataToPage(s, 15, s->size()); + delete s; + return _screen->getCPagePtr(15); +} + +Common::String EoBCoreEngine::getBlockFileName(int levelIndex, int sub) { + readLevelFileData(levelIndex); + const uint8 *data = _screen->getCPagePtr(5) + 2; + const uint8 *pos = data; + + for (int i = 0; i < sub; i++) + pos = data + READ_LE_UINT16(pos); + + pos += 2; + + if (*pos++ == 0xEC || _flags.gameID == GI_EOB1) { + if (_flags.gameID == GI_EOB1) + pos -= 3; + + return Common::String((const char *)pos); + } + + return Common::String(); +} + +const uint8 *EoBCoreEngine::getBlockFileData(const char *mazFile) { + _curBlockFile = mazFile; + return getBlockFileData(); +} + +void EoBCoreEngine::loadDecorations(const char *cpsFile, const char *decFile) { + _screen->loadShapeSetBitmap(cpsFile, 5, 3); + Common::SeekableReadStream *s = _res->createReadStream(decFile); + + _levelDecorationDataSize = s->readUint16LE(); + delete[] _levelDecorationData; + _levelDecorationData = new LevelDecorationProperty[_levelDecorationDataSize]; + memset(_levelDecorationData, 0, _levelDecorationDataSize * sizeof(LevelDecorationProperty)); + + for (int i = 0; i < _levelDecorationDataSize; i++) { + LevelDecorationProperty *l = &_levelDecorationData[i]; + for (int ii = 0; ii < 10; ii++) { + l->shapeIndex[ii] = s->readByte(); + if (l->shapeIndex[ii] == 0xFF) + l->shapeIndex[ii] = 0xFFFF; + } + l->next = s->readByte(); + l->flags = s->readByte(); + for (int ii = 0; ii < 10; ii++) + l->shapeX[ii] = s->readSint16LE(); + for (int ii = 0; ii < 10; ii++) + l->shapeY[ii] = s->readSint16LE(); + } + + int len = s->readUint16LE(); + delete[] _levelDecorationRects; + _levelDecorationRects = new EoBRect8[len]; + for (int i = 0; i < len; i++) { + EoBRect8 *l = &_levelDecorationRects[i]; + l->x = s->readUint16LE(); + l->y = s->readUint16LE(); + l->w = s->readUint16LE(); + l->h = s->readUint16LE(); + } + + delete s; +} + +void EoBCoreEngine::assignWallsAndDecorations(int wallIndex, int vmpIndex, int decIndex, int specialType, int flags) { + _wllVmpMap[wallIndex] = vmpIndex; + for (int i = 0; i < 6; i++) { + for (int ii = 0; ii < 10; ii++) { + if (_characters[i].events[ii] == -57) + spellCallback_start_trueSeeing(); + } + } + _wllShapeMap[wallIndex] = _mappedDecorationsCount + 1; + _specialWallTypes[wallIndex] = specialType; + _wllWallFlags[wallIndex] = flags ^ 4; + + if (decIndex == -1) { + _wllShapeMap[wallIndex] = 0; + return; + } + + do { + assert(decIndex < _levelDecorationDataSize); + memcpy(&_levelDecorationProperties[_mappedDecorationsCount], &_levelDecorationData[decIndex], sizeof(LevelDecorationProperty)); + + for (int i = 0; i < 10; i++) { + uint16 t = _levelDecorationProperties[_mappedDecorationsCount].shapeIndex[i]; + if (t == 0xFFFF) + continue; + + if (_levelDecorationShapes[t]) + continue; + + EoBRect8 *r = &_levelDecorationRects[t]; + if (r->w == 0 || r->h == 0) + error("Error trying to make decoration %d (x: %d, y: %d, w: %d, h: %d)", decIndex, r->x, r->y, r->w, r->h); + + _levelDecorationShapes[t] = _screen->encodeShape(r->x, r->y, r->w, r->h, false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); + } + + decIndex = _levelDecorationProperties[_mappedDecorationsCount++].next; + + if (decIndex) + _levelDecorationProperties[_mappedDecorationsCount - 1].next = _mappedDecorationsCount + 1; + else + decIndex = -1; + + } while (decIndex != -1); +} + +void EoBCoreEngine::releaseDecorations() { + if (_levelDecorationShapes) { + for (int i = 0; i < 400; i++) { + delete[] _levelDecorationShapes[i]; + _levelDecorationShapes[i] = 0; + } + } + _mappedDecorationsCount = 0; +} + +void EoBCoreEngine::releaseDoorShapes() { + for (int i = 0; i < 6; i++) { + delete[] _doorShapes[i]; + _doorShapes[i] = 0; + delete[] _doorSwitches[i].shp; + _doorSwitches[i].shp = 0; + } +} + +void EoBCoreEngine::toggleWallState(int wall, int toggle) { + wall = wall * 10 + 3; + + for (int i = 0; i < 9 ; i++) { + if (i == 4) + continue; + + if (toggle) + _wllWallFlags[wall + i] |= 2; + else + _wllWallFlags[wall + i] &= 0xFD; + } +} + +void EoBCoreEngine::drawScene(int refresh) { + generateBlockDrawingBuffer(); + drawVcnBlocks(); + drawSceneShapes(); + + if (_sceneDrawPage2) { + if (refresh) + _screen->fillRect(0, 0, 176, 120, 12); + + if (!_loading) + _screen->setScreenPalette(_screen->getPalette(0)); + + _sceneDrawPage2 = 0; + } + + uint32 ct = _system->getMillis(); + if (_flashShapeTimer > ct) { + int diff = _flashShapeTimer - ct; + while ((diff > 0) && !shouldQuit()) { + updateInput(); + uint32 step = MIN(diff, _tickLength / 5); + _system->delayMillis(step); + diff -= step; + } + } + + if (_sceneDefaultUpdate) + delayUntil(_drawSceneTimer); + + if (refresh && !_partyResting) + _screen->copyRegion(0, 0, 0, 0, 176, 120, 2, 0, Screen::CR_NO_P_CHECK); + + updateEnvironmentalSfx(0); + + if (!_dialogueField && refresh && !_updateFlags) + gui_drawCompass(false); + + if (refresh && !_partyResting && !_loading) + _screen->updateScreen(); + + if (_sceneDefaultUpdate) { + _sceneDefaultUpdate = false; + _drawSceneTimer = _system->getMillis() + 4 * _tickLength; + } + + _sceneUpdateRequired = false; +} + +void EoBCoreEngine::drawSceneShapes(int start) { + for (int i = start; i < 18; i++) { + uint8 t = _dscTileIndex[i]; + uint8 s = _visibleBlocks[t]->walls[_sceneDrawVarDown]; + + _shpDmX1 = 0; + _shpDmX2 = 0; + + setLevelShapesDim(t, _shpDmX1, _shpDmX2, _sceneShpDim); + + if (_shpDmX2 <= _shpDmX1) + continue; + + drawDecorations(t); + + if (_visibleBlocks[t]->drawObjects) + drawBlockItems(t); + + if (t < 15) { + uint16 w = _wllWallFlags[s]; + + if (w & 8) + drawDoor(t); + + if (_visibleBlocks[t]->flags & 7) { + const ScreenDim *dm = _screen->getScreenDim(5); + _screen->modifyScreenDim(5, dm->sx, _lvlShapeTop[t], dm->w, _lvlShapeBottom[t] - _lvlShapeTop[t]); + drawMonsters(t); + drawLevelModifyScreenDim(5, _lvlShapeLeftRight[(t << 1)], 0, _lvlShapeLeftRight[(t << 1) + 1], 15); + } + + if (_flags.gameID == GI_EOB2 && s == 74) + drawWallOfForce(t); + } + + drawFlyingObjects(t); + + if (s == _teleporterWallId) + drawTeleporter(t); + } +} + +void EoBCoreEngine::drawDecorations(int index) { + for (int i = 1; i >= 0; i--) { + int s = index * 2 + i; + if (_dscWallMapping[s]) { + int16 d = *_dscWallMapping[s]; + int8 l = _wllShapeMap[_visibleBlocks[index]->walls[d]]; + + uint8 *shapeData = 0; + + int x = 0; + + while (l > 0) { + l--; + int8 ix = _dscShapeIndex[s]; + uint8 shpIx = ABS(ix) - 1; + uint8 flg = _levelDecorationProperties[l].flags; + + if ((i == 0) && (flg & 1 || ((flg & 2) && _wllProcessFlag))) + ix = -ix; + + if (_levelDecorationProperties[l].shapeIndex[shpIx] == 0xFFFF) { + l = _levelDecorationProperties[l].next; + continue; + } + + shapeData = _levelDecorationShapes[_levelDecorationProperties[l].shapeIndex[shpIx]]; + if (shapeData) { + x = 0; + if (i == 0) { + if (flg & 4) + x += _dscShapeCoords[(index * 5 + 4) << 1]; + else + x += _dscShapeX[index]; + } + + if (ix < 0) { + x += (176 - _levelDecorationProperties[l].shapeX[shpIx] - (shapeData[2] << 3)); + drawBlockObject(1, 2, shapeData, x, _levelDecorationProperties[l].shapeY[shpIx], _sceneShpDim); + } else { + x += _levelDecorationProperties[l].shapeX[shpIx]; + drawBlockObject(0, 2, shapeData, x, _levelDecorationProperties[l].shapeY[shpIx], _sceneShpDim); + + } + } + l = _levelDecorationProperties[l].next; + continue; + } + } + } +} + +int EoBCoreEngine::calcNewBlockPositionAndTestPassability(uint16 curBlock, uint16 direction) { + uint16 b = calcNewBlockPosition(curBlock, direction); + int w = _levelBlockProperties[b].walls[direction ^ 2]; + int f = _wllWallFlags[w]; + + assert((_flags.gameID == GI_EOB1 && w < 70) || (_flags.gameID == GI_EOB2 && w < 80)); + + if (_flags.gameID == GI_EOB2 && w == 74 && _currentBlock == curBlock) { + for (int i = 0; i < 5; i++) { + if (_wallsOfForce[i].block == b) { + destroyWallOfForce(i); + f = _wllWallFlags[0]; + } + } + } + + if (!(f & 1) || _levelBlockProperties[b].flags & 7) + return -1; + + return b; +} + +void EoBCoreEngine::notifyBlockNotPassable() { + _txt->printMessage(_warningStrings[0]); + snd_playSoundEffect(29); + removeInputTop(); +} + +void EoBCoreEngine::moveParty(uint16 block) { + updateAllMonsterDests(); + uint16 old = _currentBlock; + _currentBlock = block; + + runLevelScript(old, 2); + + if (++_moveCounter > 3) { + _txt->printMessage("\r"); + _moveCounter = 0; + } + + runLevelScript(block, 1); + + if (_flags.gameID == GI_EOB2 && _levelBlockProperties[block].walls[0] == 26) + memset(_levelBlockProperties[block].walls, 0, 4); + + updateAllMonsterDests(); + _stepCounter++; + //_keybControlUnk = -1; + _sceneUpdateRequired = true; + + checkFlyingObjects(); +} + +int EoBCoreEngine::clickedDoorSwitch(uint16 block, uint16 direction) { + uint8 v = _visibleBlocks[13]->walls[_sceneDrawVarDown]; + SpriteDecoration *d = &_doorSwitches[((v > 12 && v < 23) || v == 31) ? 3 : 0]; + int x1 = d->x + _dscShapeCoords[138] - 4; + int y1 = d->y - 4; + + if (_flags.gameID == GI_EOB1 && _currentLevel >= 4 && _currentLevel <= 6) { + if (v >= 30) + x1 += 4; + else + x1 += ((v - _dscDoorXE[v]) * 9); + } + + if (!posWithinRect(_mouseX, _mouseY, x1, y1, x1 + (d->shp[2] << 3) + 8, y1 + d->shp[1] + 8) && (_clickedSpecialFlag == 0x40)) + return clickedDoorNoPry(block, direction); + + processDoorSwitch(block, 0); + snd_playSoundEffect(6); + + return 1; +} + +int EoBCoreEngine::clickedNiche(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v)) + return 0; + + if (_itemInHand) { + if (_dscItemShapeMap[_items[_itemInHand].icon] <= 14) { + _txt->printMessage(_pryDoorStrings[5]); + } else { + setItemPosition((Item *)&_levelBlockProperties[block & 0x3FF].drawObjects, block, _itemInHand, 8); + runLevelScript(block, 4); + setHandItem(0); + _sceneUpdateRequired = true; + } + } else { + int d = getQueuedItem((Item *)&_levelBlockProperties[block].drawObjects, 8, -1); + if (!d) + return 1; + runLevelScript(block, 8); + setHandItem(d); + _sceneUpdateRequired = true; + } + + return 1; +} + +int EoBCoreEngine::clickedDoorPry(uint16 block, uint16 direction) { + if (!posWithinRect(_mouseX, _mouseY, 40, 16, 136, 88) && (_clickedSpecialFlag == 0x40)) + return 0; + + int d = -1; + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 0x0D)) + continue; + if (d >= 0) { + int s1 = _characters[i].strengthCur + _characters[i].strengthExtCur; + int s2 = _characters[d].strengthCur + _characters[d].strengthExtCur; + if (s1 >= s2) + d = i; + } else { + d = i; + } + } + + if (d == -1) { + _txt->printMessage(_pryDoorStrings[_flags.gameID == GI_EOB2 ? 1 : 0]); + return 1; + } + + static const uint8 forceDoorChanceTable[] = { 1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 10, 11, 12, 13 }; + int s = _characters[d].strengthCur > 18 ? 18 : _characters[d].strengthCur; + + if (rollDice(1, 20) < forceDoorChanceTable[s]) { + _txt->printMessage(_pryDoorStrings[_flags.gameID == GI_EOB2 ? 2 : 1]); + _levelBlockProperties[block].walls[direction] = _levelBlockProperties[block].walls[direction ^ 2] = + (_levelBlockProperties[block].walls[direction] == (_flags.gameID == GI_EOB2 ? 51 : 30)) ? 8 : 18; + openDoor(block); + } else { + _txt->printMessage(_pryDoorStrings[3]); + } + + return 1; +} + +int EoBCoreEngine::clickedDoorNoPry(uint16 block, uint16 direction) { + if (!posWithinRect(_mouseX, _mouseY, 40, 16, 136, 88) && (_clickedSpecialFlag == 0x40)) + return 0; + + if (!(_wllWallFlags[_levelBlockProperties[block].walls[direction]] & 0x20)) + return 0; + _txt->printMessage(_pryDoorStrings[6]); + return 1; +} + +int EoBCoreEngine::specialWallAction(int block, int direction) { + direction ^= 2; + uint8 type = _specialWallTypes[_levelBlockProperties[block].walls[direction]]; + if (!type || !(_clickedSpecialFlag & (((_levelBlockProperties[block].flags & 0xF8) >> 3) | 0xE0))) + return 0; + + int res = 0; + switch (type) { + case 1: + res = clickedDoorSwitch(block, direction); + break; + + case 2: + case 8: + res = clickedWallShape(block, direction); + break; + + case 3: + res = clickedLeverOn(block, direction); + break; + + case 4: + res = clickedLeverOff(block, direction); + break; + + case 5: + res = clickedDoorPry(block, direction); + break; + + case 6: + res = clickedDoorNoPry(block, direction); + break; + + case 7: + case 9: + res = clickedWallOnlyScript(block); + break; + + case 10: + res = clickedNiche(block, direction); + break; + + default: + break; + } + + _clickedSpecialFlag = 0; + _sceneUpdateRequired = true; + + return res; +} + +void EoBCoreEngine::openDoor(int block) { + openCloseDoor(block, 1); +} + +void EoBCoreEngine::closeDoor(int block) { + if (block == _currentBlock || _levelBlockProperties[block].flags & 7) + return; + openCloseDoor(block, -1); +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/scene_hof.cpp b/engines/kyra/engine/scene_hof.cpp new file mode 100644 index 0000000000..e4747fd7d5 --- /dev/null +++ b/engines/kyra/engine/scene_hof.cpp @@ -0,0 +1,737 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_hof.h" +#include "kyra/sound/sound.h" +#include "kyra/resource/resource.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_HoF::enterNewScene(uint16 newScene, int facing, int unk1, int unk2, int unk3) { + if (_newChapterFile != _currentTalkFile) { + _currentTalkFile = _newChapterFile; + if (_flags.isTalkie) { + showMessageFromCCode(265, 150, 0); + _screen->updateScreen(); + openTalkFile(_currentTalkFile); + } + showMessage(0, 207); + _screen->updateScreen(); + } + + _screen->hideMouse(); + + if (!unk3) { + updateWaterFlasks(); + displayInvWsaLastFrame(); + } + + if (unk1) { + int x = _mainCharacter.x1; + int y = _mainCharacter.y1; + + switch (facing) { + case 0: + y -= 6; + break; + + case 2: + x = 335; + break; + + case 4: + y = 147; + break; + + case 6: + x = -16; + break; + + default: + break; + } + + moveCharacter(facing, x, y); + } + + // TODO: Check how the original handled sfx still playing + _sound->stopAllSoundEffects(); + + bool newSoundFile = false; + uint32 waitTime = 0; + if (_sceneList[newScene].sound != _lastMusicCommand) { + newSoundFile = true; + waitTime = _system->getMillis() + 1000; + _sound->beginFadeOut(); + } + + _chatAltFlag = false; + + if (!unk3) { + _emc->init(&_sceneScriptState, &_sceneScriptData); + _emc->start(&_sceneScriptState, 5); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + } + + Common::for_each(_wsaSlots, ARRAYEND(_wsaSlots), Common::mem_fun(&WSAMovie_v2::close)); + _specialExitCount = 0; + memset(_specialExitTable, -1, sizeof(_specialExitTable)); + + _mainCharacter.sceneId = newScene; + _sceneList[newScene].flags &= ~1; + loadScenePal(); + unloadScene(); + loadSceneMsc(); + + SceneDesc &scene = _sceneList[newScene]; + _sceneExit1 = scene.exit1; + _sceneExit2 = scene.exit2; + _sceneExit3 = scene.exit3; + _sceneExit4 = scene.exit4; + + if (newSoundFile) { + if (_sound->getMusicType() == Sound::kAdLib) { + while (_sound->isPlaying()) + _system->delayMillis(10); + } else { + while (waitTime > _system->getMillis()) + _system->delayMillis(10); + } + snd_loadSoundFile(_sceneList[newScene].sound); + } + + startSceneScript(unk3); + + if (_overwriteSceneFacing) { + facing = _mainCharacter.facing; + _overwriteSceneFacing = false; + } + + enterNewSceneUnk1(facing, unk2, unk3); + + setTimer1DelaySecs(-1); + _sceneScriptState.regs[3] = 1; + enterNewSceneUnk2(unk3); + _screen->showMouse(); + _unk5 = 0; + setNextIdleAnimTimer(); + + _currentScene = newScene; +} + +void KyraEngine_HoF::enterNewSceneUnk1(int facing, int unk1, int unk2) { + int x = 0, y = 0; + int x2 = 0, y2 = 0; + bool needProc = true; + + if (_mainCharX == -1 && _mainCharY == -1) { + switch (facing+1) { + case 1: case 2: case 8: + x2 = _sceneEnterX3; + y2 = _sceneEnterY3; + break; + + case 3: + x2 = _sceneEnterX4; + y2 = _sceneEnterY4; + break; + + case 4: case 5: case 6: + x2 = _sceneEnterX1; + y2 = _sceneEnterY1; + break; + + case 7: + x2 = _sceneEnterX2; + y2 = _sceneEnterY2; + break; + + default: + x2 = y2 = -1; + } + + if (x2 >= 316) + x2 = 312; + if (y2 >= 141) + y2 = 139; + if (x2 <= 4) + x2 = 8; + } + + if (_mainCharX >= 0) { + x = x2 = _mainCharX; + needProc = false; + } + + if (_mainCharY >= 0) { + y = y2 = _mainCharY; + needProc = false; + } + + _mainCharX = _mainCharY = -1; + + if (unk1 && needProc) { + x = x2; + y = y2; + + switch (facing) { + case 0: + y2 = 147; + break; + + case 2: + x2 = -16; + break; + + case 4: + y2 = y - 4; + break; + + case 6: + x2 = 335; + break; + + default: + break; + } + } + + x2 &= ~3; + x &= ~3; + y2 &= ~1; + y &= ~1; + + _mainCharacter.facing = facing; + _mainCharacter.x1 = _mainCharacter.x2 = x2; + _mainCharacter.y1 = _mainCharacter.y2 = y2; + initSceneAnims(unk2); + + if (!unk2) + snd_playWanderScoreViaMap(_sceneList[_mainCharacter.sceneId].sound, 0); + + if (unk1 && !unk2 && _mainCharacter.animFrame != 32) + moveCharacter(facing, x, y); +} + +void KyraEngine_HoF::enterNewSceneUnk2(int unk1) { + _savedMouseState = -1; + + if (_flags.isTalkie) { + if (_mainCharX == -1 && _mainCharY == -1 && _mainCharacter.sceneId != 61 && + !queryGameFlag(0x1F1) && !queryGameFlag(0x192) && !queryGameFlag(0x193) && + _mainCharacter.sceneId != 70 && !queryGameFlag(0x159) && _mainCharacter.sceneId != 37) { + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); + } + } else if (_mainCharX != -1 && _mainCharY != -1) { + if (_characterFrameTable[_mainCharacter.facing] == 25) + _mainCharacter.facing = 5; + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); + } + + if (!unk1) { + runSceneScript4(0); + zanthSceneStartupChat(); + } + + _unk4 = 0; + _savedMouseState = -1; +} + +int KyraEngine_HoF::trySceneChange(int *moveTable, int unk1, int updateChar) { + bool running = true; + bool unkFlag = false; + int8 updateType = -1; + int changedScene = 0; + const int *moveTableStart = moveTable; + _unk4 = 0; + while (running && !shouldQuit()) { + if (*moveTable >= 0 && *moveTable <= 7) { + _mainCharacter.facing = getOppositeFacingDirection(*moveTable); + unkFlag = true; + } else { + if (*moveTable == 8) { + running = false; + } else { + ++moveTable; + unkFlag = false; + } + } + + if (checkSceneChange()) { + running = false; + changedScene = 1; + } + + if (unk1) { + if (skipFlag()) { + resetSkipFlag(false); + running = false; + _unk4 = 1; + } + } + + if (!unkFlag || !running) + continue; + + int ret = 0; + if (moveTable == moveTableStart || moveTable[1] == 8) + ret = updateCharPos(0); + else + ret = updateCharPos(moveTable); + + if (ret) + ++moveTable; + + ++updateType; + if (!updateType) { + update(); + } else if (updateType == 1) { + refreshAnimObjectsIfNeed(); + updateType = -1; + } + + delay(10); + } + + if (updateChar) + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); + + return changedScene; +} + +int KyraEngine_HoF::checkSceneChange() { + SceneDesc &curScene = _sceneList[_mainCharacter.sceneId]; + int charX = _mainCharacter.x1, charY = _mainCharacter.y1; + int facing = 0; + int process = 0; + + if (_screen->getLayer(charX, charY) == 1 && _savedMouseState == -6) { + facing = 0; + process = 1; + } else if (charX >= 316 && _savedMouseState == -5) { + facing = 2; + process = 1; + } else if (charY >= 142 && _savedMouseState == -4) { + facing = 4; + process = 1; + } else if (charX <= 4 && _savedMouseState == -3) { + facing = 6; + process = 1; + } + + if (!process) + return 0; + + uint16 newScene = 0xFFFF; + switch (facing) { + case 0: + newScene = curScene.exit1; + break; + + case 2: + newScene = curScene.exit2; + break; + + case 4: + newScene = curScene.exit3; + break; + + case 6: + newScene = curScene.exit4; + break; + + default: + newScene = _mainCharacter.sceneId; + } + + if (newScene == 0xFFFF) + return 0; + + enterNewScene(newScene, facing, 1, 1, 0); + return 1; +} + +void KyraEngine_HoF::unloadScene() { + _emc->unload(&_sceneScriptData); + freeSceneShapePtrs(); + freeSceneAnims(); +} + +void KyraEngine_HoF::loadScenePal() { + uint16 sceneId = _mainCharacter.sceneId; + _screen->copyPalette(1, 0); + + char filename[14]; + strcpy(filename, _sceneList[sceneId].filename1); + strcat(filename, ".COL"); + _screen->loadBitmap(filename, 3, 3, 0); + _screen->getPalette(1).copy(_screen->getCPagePtr(3), 0, 128); + _screen->getPalette(1).fill(0, 1, 0); + memcpy(_scenePal, _screen->getCPagePtr(3)+336, 432); +} + +void KyraEngine_HoF::loadSceneMsc() { + uint16 sceneId = _mainCharacter.sceneId; + char filename[14]; + strcpy(filename, _sceneList[sceneId].filename1); + strcat(filename, ".MSC"); + _screen->loadBitmap(filename, 3, 5, 0); +} + +void KyraEngine_HoF::startSceneScript(int unk1) { + uint16 sceneId = _mainCharacter.sceneId; + char filename[14]; + + strcpy(filename, _sceneList[sceneId].filename1); + if (sceneId == 68 && (queryGameFlag(0x1BC) || queryGameFlag(0x1BD))) + strcpy(filename, "DOORX"); + strcat(filename, ".CPS"); + + _screen->loadBitmap(filename, 3, 3, 0); + resetScaleTable(); + _useCharPal = false; + memset(_charPalTable, 0, sizeof(_charPalTable)); + memset(_layerFlagTable, 0, sizeof(_layerFlagTable)); + memset(_specialSceneScriptState, 0, sizeof(_specialSceneScriptState)); + + _sceneEnterX1 = 160; + _sceneEnterY1 = 0; + _sceneEnterX2 = 296; + _sceneEnterY2 = 72; + _sceneEnterX3 = 160; + _sceneEnterY3 = 128; + _sceneEnterX4 = 24; + _sceneEnterY4 = 72; + + _sceneCommentString = "Undefined scene comment string!"; + _emc->init(&_sceneScriptState, &_sceneScriptData); + + strcpy(filename, _sceneList[sceneId].filename1); + strcat(filename, "."); + strcat(filename, _scriptLangExt[(_flags.platform == Common::kPlatformDOS && !_flags.isTalkie) ? 0 : _lang]); + + _res->exists(filename, true); + _emc->load(filename, &_sceneScriptData, &_opcodes); + runSceneScript7(); + + _emc->start(&_sceneScriptState, 0); + _sceneScriptState.regs[0] = sceneId; + _sceneScriptState.regs[5] = unk1; + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + memcpy(_gamePlayBuffer, _screen->getCPagePtr(3), 46080); + + for (int i = 0; i < 10; ++i) { + _emc->init(&_sceneSpecialScripts[i], &_sceneScriptData); + _emc->start(&_sceneSpecialScripts[i], i+8); + _sceneSpecialScriptsTimer[i] = 0; + } + + _sceneEnterX1 &= ~3; + _sceneEnterX2 &= ~3; + _sceneEnterX3 &= ~3; + _sceneEnterX4 &= ~3; + _sceneEnterY1 &= ~1; + _sceneEnterY2 &= ~1; + _sceneEnterY3 &= ~1; + _sceneEnterY4 &= ~1; +} + +void KyraEngine_HoF::runSceneScript2() { + _emc->init(&_sceneScriptState, &_sceneScriptData); + _sceneScriptState.regs[4] = _itemInHand; + _emc->start(&_sceneScriptState, 2); + + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +void KyraEngine_HoF::runSceneScript4(int unk1) { + _sceneScriptState.regs[4] = _itemInHand; + _sceneScriptState.regs[5] = unk1; + + _emc->start(&_sceneScriptState, 4); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +void KyraEngine_HoF::runSceneScript7() { + int oldPage = _screen->_curPage; + _screen->_curPage = 2; + + _emc->start(&_sceneScriptState, 7); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + _screen->_curPage = oldPage; +} + +void KyraEngine_HoF::initSceneAnims(int unk1) { + for (int i = 0; i < 41; ++i) + _animObjects[i].enabled = 0; + + bool animInit = false; + + AnimObj *animState = &_animObjects[0]; + + if (_mainCharacter.animFrame != 32) + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + + animState->enabled = 1; + animState->xPos1 = _mainCharacter.x1; + animState->yPos1 = _mainCharacter.y1; + animState->shapePtr = getShapePtr(_mainCharacter.animFrame); + animState->shapeIndex1 = animState->shapeIndex2 = _mainCharacter.animFrame; + + int frame = _mainCharacter.animFrame - 9; + int shapeX = _shapeDescTable[frame].xAdd; + int shapeY = _shapeDescTable[frame].yAdd; + + animState->xPos2 = _mainCharacter.x1; + animState->yPos2 = _mainCharacter.y1; + + _charScale = getScale(_mainCharacter.x1, _mainCharacter.y1); + + int shapeXScaled = (shapeX * _charScale) >> 8; + int shapeYScaled = (shapeY * _charScale) >> 8; + + animState->xPos2 += shapeXScaled; + animState->yPos2 += shapeYScaled; + animState->xPos3 = animState->xPos2; + animState->yPos3 = animState->yPos2; + animState->needRefresh = 1; + animState->specialRefresh = 1; + + _animList = 0; + + AnimObj *charAnimState = animState; + + for (int i = 0; i < 10; ++i) { + animState = &_animObjects[i+1]; + animState->enabled = 0; + animState->needRefresh = 0; + animState->specialRefresh = 0; + + if (_sceneAnims[i].flags & 1) { + animState->enabled = 1; + animState->needRefresh = 1; + animState->specialRefresh = 1; + } + + animState->animFlags = _sceneAnims[i].flags & 8; + + if (_sceneAnims[i].flags & 2) + animState->flags = 0x800; + else + animState->flags = 0; + + if (_sceneAnims[i].flags & 4) + animState->flags |= 1; + + animState->xPos1 = _sceneAnims[i].x; + animState->yPos1 = _sceneAnims[i].y; + + if (_sceneAnims[i].flags & 0x20) + animState->shapePtr = _sceneShapeTable[_sceneAnims[i].shapeIndex]; + else + animState->shapePtr = 0; + + if (_sceneAnims[i].flags & 0x40) { + animState->shapeIndex3 = _sceneAnims[i].shapeIndex; + animState->animNum = i; + } else { + animState->shapeIndex3 = 0xFFFF; + animState->animNum = 0xFFFF; + } + + animState->shapeIndex2 = 0xFFFF; + + animState->xPos3 = animState->xPos2 = _sceneAnims[i].x2; + animState->yPos3 = animState->yPos2 = _sceneAnims[i].y2; + animState->width = _sceneAnims[i].width; + animState->height = _sceneAnims[i].height; + animState->width2 = animState->height2 = _sceneAnims[i].specialSize; + + if (_sceneAnims[i].flags & 1) { + if (animInit) { + _animList = addToAnimListSorted(_animList, animState); + } else { + _animList = initAnimList(_animList, animState); + animInit = true; + } + } + } + + if (animInit) { + _animList = addToAnimListSorted(_animList, charAnimState); + } else { + _animList = initAnimList(_animList, charAnimState); + animInit = true; + } + + for (int i = 0; i < 30; ++i) { + animState = &_animObjects[i+11]; + + uint16 shapeIndex = _itemList[i].id; + if (shapeIndex == 0xFFFF || _itemList[i].sceneId != _mainCharacter.sceneId) { + animState->enabled = 0; + animState->needRefresh = 0; + animState->specialRefresh = 0; + } else { + animState->xPos1 = _itemList[i].x; + animState->yPos1 = _itemList[i].y; + animState->shapePtr = getShapePtr(64+shapeIndex); + animState->shapeIndex1 = animState->shapeIndex2 = shapeIndex+64; + + animState->xPos2 = _itemList[i].x; + animState->yPos2 = _itemList[i].y; + int objectScale = getScale(animState->xPos2, animState->yPos2); + + const uint8 *shape = getShapePtr(animState->shapeIndex1); + animState->xPos2 -= (_screen->getShapeScaledWidth(shape, objectScale) >> 1); + animState->yPos2 -= (_screen->getShapeScaledHeight(shape, objectScale) >> 1); + animState->xPos3 = animState->xPos2; + animState->yPos3 = animState->yPos2; + + animState->enabled = 1; + animState->needRefresh = 1; + animState->specialRefresh = 1; + + if (animInit) { + _animList = addToAnimListSorted(_animList, animState); + } else { + _animList = initAnimList(_animList, animState); + animInit = true; + } + } + } + + _animObjects[0].specialRefresh = 1; + _animObjects[0].needRefresh = 1; + + for (int i = 1; i < 41; ++i) { + if (_animObjects[i].enabled) { + _animObjects[i].needRefresh = 1; + _animObjects[i].specialRefresh = 1; + } + } + + restorePage3(); + drawAnimObjects(); + _screen->hideMouse(); + initSceneScreen(unk1); + _screen->showMouse(); + refreshAnimObjects(0); +} + +void KyraEngine_HoF::initSceneScreen(int unk1) { + if (_unkSceneScreenFlag1) { + _screen->copyRegion(0, 0, 0, 0, 320, 144, 2, 0, Screen::CR_NO_P_CHECK); + return; + } + + if (_noScriptEnter) { + _screen->getPalette(0).fill(0, 128, 0); + _screen->setScreenPalette(_screen->getPalette(0)); + } + + _screen->copyRegion(0, 0, 0, 0, 320, 144, 2, 0, Screen::CR_NO_P_CHECK); + + if (_noScriptEnter) { + _screen->setScreenPalette(_screen->getPalette(1)); + _screen->getPalette(0).copy(_screen->getPalette(1), 0, 128); + } + + updateCharPal(0); + + _emc->start(&_sceneScriptState, 3); + _sceneScriptState.regs[5] = unk1; + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +void KyraEngine_HoF::freeSceneShapePtrs() { + for (int i = 0; i < ARRAYSIZE(_sceneShapeTable); ++i) + delete[] _sceneShapeTable[i]; + memset(_sceneShapeTable, 0, sizeof(_sceneShapeTable)); +} + +void KyraEngine_HoF::fadeScenePal(int srcIndex, int delayTime) { + _screen->getPalette(0).copy(_scenePal, srcIndex << 4, 16, 112); + _screen->fadePalette(_screen->getPalette(0), delayTime, &_updateFunctor); +} + +#pragma mark - +#pragma mark - Pathfinder +#pragma mark - + +bool KyraEngine_HoF::lineIsPassable(int x, int y) { + static const int widthTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 }; + + if (_pathfinderFlag & 2) { + if (x >= 320) + return false; + } + + if (_pathfinderFlag & 4) { + if (y >= 144) + return false; + } + + if (_pathfinderFlag & 8) { + if (x < 0) + return false; + } + + if (y > 143) + return false; + + int unk1 = widthTable[getScale(x, y) >> 5]; + + if (y < 0) + y = 0; + x -= unk1 >> 1; + if (x < 0) + x = 0; + int x2 = x + unk1; + if (x2 > 320) + x2 = 320; + + for (;x < x2; ++x) + if (!_screen->getShapeFlag1(x, y)) + return false; + + return true; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/scene_lok.cpp b/engines/kyra/engine/scene_lok.cpp new file mode 100644 index 0000000000..51348c5392 --- /dev/null +++ b/engines/kyra/engine/scene_lok.cpp @@ -0,0 +1,1301 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_lok.h" +#include "kyra/resource/resource.h" +#include "kyra/sound/sound.h" +#include "kyra/engine/sprites.h" +#include "kyra/graphics/animator_lok.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_LoK::enterNewScene(int sceneId, int facing, int unk1, int unk2, int brandonAlive) { + int unkVar1 = 1; + _screen->hideMouse(); + + // TODO: Check how the original handled sfx still playing + _sound->stopAllSoundEffects(); + + if (_flags.platform == Common::kPlatformFMTowns) { + int newSfxFile = -1; + if (_currentCharacter->sceneId == 7 && sceneId == 24) + newSfxFile = 2; + else if (_currentCharacter->sceneId == 25 && sceneId == 109) + newSfxFile = 3; + else if (_currentCharacter->sceneId == 120 && sceneId == 37) + newSfxFile = 4; + else if (_currentCharacter->sceneId == 52 && sceneId == 199) + newSfxFile = 5; + else if (_currentCharacter->sceneId == 37 && sceneId == 120) + newSfxFile = 3; + else if (_currentCharacter->sceneId == 109 && sceneId == 25) + newSfxFile = 2; + else if (_currentCharacter->sceneId == 24 && sceneId == 7) + newSfxFile = 1; + + if (newSfxFile != -1) { + _curSfxFile = newSfxFile; + _sound->loadSoundFile(_curSfxFile); + } + } + + switch (_currentCharacter->sceneId) { + case 1: + if (sceneId == 0) { + moveCharacterToPos(0, 0, _currentCharacter->x1, 84); + unkVar1 = 0; + } + break; + + case 3: + if (sceneId == 2) { + moveCharacterToPos(0, 6, 155, _currentCharacter->y1); + unkVar1 = 0; + } + break; + + case 26: + if (sceneId == 27) { + moveCharacterToPos(0, 6, 155, _currentCharacter->y1); + unkVar1 = 0; + } + break; + + case 44: + if (sceneId == 45) { + moveCharacterToPos(0, 2, 192, _currentCharacter->y1); + unkVar1 = 0; + } + break; + + default: + break; + } + + if (unkVar1 && unk1) { + int xpos = _currentCharacter->x1; + int ypos = _currentCharacter->y1; + switch (facing) { + case 0: + ypos = _currentCharacter->y1 - 6; + break; + + case 2: + xpos = 336; + break; + + case 4: + ypos = 143; + break; + + case 6: + xpos = -16; + break; + + default: + break; + } + + moveCharacterToPos(0, facing, xpos, ypos); + } + + for (int i = 0; i < ARRAYSIZE(_movieObjects); ++i) + _movieObjects[i]->close(); + + if (!brandonAlive) { + _emc->init(&_scriptClick, &_scriptClickData); + _emc->start(&_scriptClick, 5); + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); + } + + memset(_entranceMouseCursorTracks, 0xFF, sizeof(_entranceMouseCursorTracks)); + _currentCharacter->sceneId = sceneId; + + assert(sceneId < _roomTableSize); + assert(_roomTable[sceneId].nameIndex < _roomFilenameTableSize); + + Room *currentRoom = &_roomTable[sceneId]; + + setupSceneResource(sceneId); + + _currentRoom = sceneId; + + int tableId = _roomTable[sceneId].nameIndex; + char fileNameBuffer[32]; + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".DAT"); + _sprites->loadDat(fileNameBuffer, _sceneExits); + _sprites->setupSceneAnims(); + _emc->unload(&_scriptClickData); + loadSceneMsc(); + + _walkBlockNorth = currentRoom->northExit; + _walkBlockEast = currentRoom->eastExit; + _walkBlockSouth = currentRoom->southExit; + _walkBlockWest = currentRoom->westExit; + + if (_walkBlockNorth == 0xFFFF) + _screen->blockOutRegion(0, 0, 320, (_northExitHeight & 0xFF) + 3); + if (_walkBlockEast == 0xFFFF) + _screen->blockOutRegion(312, 0, 8, 139); + if (_walkBlockSouth == 0xFFFF) + _screen->blockOutRegion(0, 135, 320, 8); + if (_walkBlockWest == 0xFFFF) + _screen->blockOutRegion(0, 0, 8, 139); + + if (!brandonAlive) + updatePlayerItemsForScene(); + + startSceneScript(brandonAlive); + setupSceneItems(); + + initSceneData(facing, unk2, brandonAlive); + + _loopFlag2 = 0; + _screen->showMouse(); + if (!brandonAlive) + seq_poisonDeathNow(0); + updateMousePointer(true); + _changedScene = true; +} + +void KyraEngine_LoK::transcendScenes(int roomIndex, int roomName) { + assert(roomIndex < _roomTableSize); + + if (_flags.isTalkie) { + char file[32]; + assert(roomIndex < _roomTableSize); + int tableId = _roomTable[roomIndex].nameIndex; + assert(tableId < _roomFilenameTableSize); + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".VRM"); + _res->unloadPakFile(file); + } + + _roomTable[roomIndex].nameIndex = roomName; + _unkScreenVar2 = 1; + _unkScreenVar3 = 1; + _unkScreenVar1 = 0; + _brandonPosX = _currentCharacter->x1; + _brandonPosY = _currentCharacter->y1; + enterNewScene(roomIndex, _currentCharacter->facing, 0, 0, 0); + _unkScreenVar1 = 1; + _unkScreenVar2 = 0; + _unkScreenVar3 = 0; +} + +void KyraEngine_LoK::setSceneFile(int roomIndex, int roomName) { + assert(roomIndex < _roomTableSize); + _roomTable[roomIndex].nameIndex = roomName; +} + +void KyraEngine_LoK::moveCharacterToPos(int character, int facing, int xpos, int ypos) { + Character *ch = &_characterList[character]; + ch->facing = facing; + _screen->hideMouse(); + xpos = (int16)(xpos & 0xFFFC); + ypos = (int16)(ypos & 0xFFFE); + _timer->disable(19); + _timer->disable(14); + _timer->disable(18); + uint32 nextFrame = 0; + + switch (facing) { + case 0: + while (ypos < ch->y1) { + nextFrame = _timer->getDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + delayUntil(nextFrame, true); + } + break; + + case 2: + while (ch->x1 < xpos) { + nextFrame = _timer->getDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + delayUntil(nextFrame, true); + } + break; + + case 4: + while (ypos > ch->y1) { + nextFrame = _timer->getDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + delayUntil(nextFrame, true); + } + break; + + case 6: + while (ch->x1 > xpos) { + nextFrame = _timer->getDelay(5 + character) * _tickLength + _system->getMillis(); + setCharacterPositionWithUpdate(character); + delayUntil(nextFrame, true); + } + break; + + default: + break; + } + + _timer->enable(19); + _timer->enable(14); + _timer->enable(18); + _screen->showMouse(); +} + +void KyraEngine_LoK::setCharacterPositionWithUpdate(int character) { + setCharacterPosition(character, 0); + _sprites->updateSceneAnims(); + _timer->update(); + _animator->updateAllObjectShapes(); + updateTextFade(); + + if (_currentCharacter->sceneId == 210) + updateKyragemFading(); +} + +int KyraEngine_LoK::setCharacterPosition(int character, int *facingTable) { + if (character == 0) { + _currentCharacter->x1 += _charAddXPosTable[_currentCharacter->facing]; + _currentCharacter->y1 += _charAddYPosTable[_currentCharacter->facing]; + setCharacterPositionHelper(0, facingTable); + return 1; + } else { + _characterList[character].x1 += _charAddXPosTable[_characterList[character].facing]; + _characterList[character].y1 += _charAddYPosTable[_characterList[character].facing]; + if (_characterList[character].sceneId == _currentCharacter->sceneId) + setCharacterPositionHelper(character, 0); + } + return 0; +} + +void KyraEngine_LoK::setCharacterPositionHelper(int character, int *facingTable) { + Character *ch = &_characterList[character]; + ++ch->currentAnimFrame; + int facing = ch->facing; + if (facingTable) { + if (*facingTable != *(facingTable - 1)) { + if (*(facingTable - 1) == *(facingTable + 1)) { + facing = getOppositeFacingDirection(*(facingTable - 1)); + *facingTable = *(facingTable - 1); + } + } + } + + if (facing == 0) { + ++_characterFacingZeroCount[character]; + } else { + bool resetTables = false; + if (facing != 7) { + if (facing - 1 != 0) { + if (facing != 4) { + if (facing == 3 || facing == 5) { + if (_characterFacingFourCount[character] > 2) + facing = 4; + resetTables = true; + } + } else { + ++_characterFacingFourCount[character]; + } + } else { + if (_characterFacingZeroCount[character] > 2) + facing = 0; + resetTables = true; + } + } else { + if (_characterFacingZeroCount[character] > 2) + facing = 0; + resetTables = true; + } + + if (resetTables) { + _characterFacingZeroCount[character] = 0; + _characterFacingFourCount[character] = 0; + } + } + + static const uint16 maxAnimationFrame[] = { + 0x000F, 0x0031, 0x0055, 0x0000, 0x0000, 0x0000, + 0x0008, 0x002A, 0x004E, 0x0000, 0x0000, 0x0000, + 0x0022, 0x0046, 0x006A, 0x0000, 0x0000, 0x0000, + 0x001D, 0x0041, 0x0065, 0x0000, 0x0000, 0x0000, + 0x001F, 0x0043, 0x0067, 0x0000, 0x0000, 0x0000, + 0x0028, 0x004C, 0x0070, 0x0000, 0x0000, 0x0000, + 0x0023, 0x0047, 0x006B, 0x0000, 0x0000, 0x0000 + }; + + if (facing == 0) { + if (maxAnimationFrame[36 + character] > ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[36 + character]; + if (maxAnimationFrame[30 + character] < ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[36 + character]; + } else if (facing == 4) { + if (maxAnimationFrame[18 + character] > ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[18 + character]; + if (maxAnimationFrame[12 + character] < ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[18 + character]; + } else { + if (maxAnimationFrame[18 + character] < ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[30 + character]; + if (maxAnimationFrame[character] == ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[6 + character]; + if (maxAnimationFrame[character] < ch->currentAnimFrame) + ch->currentAnimFrame = maxAnimationFrame[6 + character] + 2; + } + + if (character == 0 && (_brandonStatusBit & 0x10)) + ch->currentAnimFrame = 88; + + _animator->animRefreshNPC(character); +} + +void KyraEngine_LoK::loadSceneMsc() { + assert(_currentCharacter->sceneId < _roomTableSize); + int tableId = _roomTable[_currentCharacter->sceneId].nameIndex; + assert(tableId < _roomFilenameTableSize); + char fileNameBuffer[32]; + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".MSC"); + _screen->fillRect(0, 0, 319, 199, 0, 5); + _res->exists(fileNameBuffer, true); + _screen->loadBitmap(fileNameBuffer, 3, 5, 0); +} + +void KyraEngine_LoK::startSceneScript(int brandonAlive) { + assert(_currentCharacter->sceneId < _roomTableSize); + int tableId = _roomTable[_currentCharacter->sceneId].nameIndex; + assert(tableId < _roomFilenameTableSize); + char fileNameBuffer[32]; + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".CPS"); + _screen->clearPage(3); + _res->exists(fileNameBuffer, true); + // FIXME: check this hack for amiga version + _screen->loadBitmap(fileNameBuffer, 3, 3, (_flags.platform == Common::kPlatformAmiga ? &_screen->getPalette(0) : 0)); + _sprites->loadSceneShapes(); + _exitListPtr = 0; + + _scaleMode = 1; + for (int i = 0; i < 145; ++i) + _scaleTable[i] = 256; + + clearNoDropRects(); + _emc->init(&_scriptClick, &_scriptClickData); + strcpy(fileNameBuffer, _roomFilenameTable[tableId]); + strcat(fileNameBuffer, ".EMC"); + _res->exists(fileNameBuffer, true); + _emc->unload(&_scriptClickData); + _emc->load(fileNameBuffer, &_scriptClickData, &_opcodes); + _emc->start(&_scriptClick, 0); + _scriptClick.regs[0] = _currentCharacter->sceneId; + _scriptClick.regs[7] = brandonAlive; + + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); +} + +void KyraEngine_LoK::initSceneData(int facing, int unk1, int brandonAlive) { + int16 xpos2 = 0; + int setFacing = 1; + + int16 xpos = 0, ypos = 0; + + if (_brandonPosX == -1 && _brandonPosY == -1) { + switch (facing + 1) { + case 0: + xpos = ypos = -1; + break; + + case 1: case 2: case 8: + xpos = _sceneExits.southXPos; + ypos = _sceneExits.southYPos; + break; + + case 3: + xpos = _sceneExits.westXPos; + ypos = _sceneExits.westYPos; + break; + + case 4: case 5: case 6: + xpos = _sceneExits.northXPos; + ypos = _sceneExits.northYPos; + break; + + case 7: + xpos = _sceneExits.eastXPos; + ypos = _sceneExits.eastYPos; + break; + + default: + break; + } + + if ((uint8)(_northExitHeight & 0xFF) + 2 >= ypos) + ypos = (_northExitHeight & 0xFF) + 4; + if (xpos >= 308) + xpos = 304; + if ((uint8)(_northExitHeight >> 8) - 2 <= ypos) + ypos = (_northExitHeight >> 8) - 4; + if (xpos <= 12) + xpos = 16; + } + + if (_brandonPosX > -1) + xpos = _brandonPosX; + if (_brandonPosY > -1) + ypos = _brandonPosY; + + int16 ypos2 = 0; + if (_brandonPosX > -1 && _brandonPosY > -1) { + switch (_currentCharacter->sceneId) { + case 1: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 4; + xpos2 = 192; + ypos2 = 104; + setFacing = 0; + unk1 = 1; + break; + + case 3: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 2; + xpos2 = 204; + ypos2 = 94; + setFacing = 0; + unk1 = 1; + break; + + case 26: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 2; + xpos2 = 192; + ypos2 = 128; + setFacing = 0; + unk1 = 1; + break; + + case 44: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 6; + xpos2 = 156; + ypos2 = 96; + setFacing = 0; + unk1 = 1; + break; + + case 37: + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + facing = 2; + xpos2 = 148; + ypos2 = 114; + setFacing = 0; + unk1 = 1; + break; + + default: + break; + } + } + + _brandonPosX = _brandonPosY = -1; + + if (unk1 && setFacing) { + ypos2 = ypos; + xpos2 = xpos; + switch (facing) { + case 0: + ypos = 142; + break; + + case 2: + xpos = -16; + break; + + case 4: + ypos = (uint8)(_northExitHeight & 0xFF) - 4; + break; + + case 6: + xpos = 336; + break; + + default: + break; + } + } + + xpos2 = (int16)(xpos2 & 0xFFFC); + ypos2 = (int16)(ypos2 & 0xFFFE); + xpos = (int16)(xpos & 0xFFFC); + ypos = (int16)(ypos & 0xFFFE); + _currentCharacter->facing = facing; + _currentCharacter->x1 = xpos; + _currentCharacter->x2 = xpos; + _currentCharacter->y1 = ypos; + _currentCharacter->y2 = ypos; + + initSceneObjectList(brandonAlive); + + if (unk1 && brandonAlive == 0) + moveCharacterToPos(0, facing, xpos2, ypos2); + + _scriptClick.regs[4] = _itemInHand; + _scriptClick.regs[7] = brandonAlive; + _emc->start(&_scriptClick, 3); + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); +} + +void KyraEngine_LoK::initSceneObjectList(int brandonAlive) { + for (int i = 0; i < 28; ++i) + _animator->actors()[i].active = 0; + + int startAnimFrame = 0; + + Animator_LoK::AnimObject *curAnimState = _animator->actors(); + curAnimState->active = 1; + curAnimState->drawY = _currentCharacter->y1; + curAnimState->sceneAnimPtr = _shapes[_currentCharacter->currentAnimFrame]; + curAnimState->animFrameNumber = _currentCharacter->currentAnimFrame; + startAnimFrame = _currentCharacter->currentAnimFrame - 7; + int xOffset = _defaultShapeTable[startAnimFrame].xOffset; + int yOffset = _defaultShapeTable[startAnimFrame].yOffset; + + if (_scaleMode) { + curAnimState->x1 = _currentCharacter->x1; + curAnimState->y1 = _currentCharacter->y1; + + _animator->_brandonScaleX = _scaleTable[_currentCharacter->y1]; + _animator->_brandonScaleY = _scaleTable[_currentCharacter->y1]; + + curAnimState->x1 += (_animator->_brandonScaleX * xOffset) >> 8; + curAnimState->y1 += (_animator->_brandonScaleY * yOffset) >> 8; + } else { + curAnimState->x1 = _currentCharacter->x1 + xOffset; + curAnimState->y1 = _currentCharacter->y1 + yOffset; + } + + curAnimState->x2 = curAnimState->x1; + curAnimState->y2 = curAnimState->y1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + _animator->clearQueue(); + _animator->addObjectToQueue(curAnimState); + + int listAdded = 0; + int addedObjects = 1; + + for (int i = 1; i < 5; ++i) { + Character *ch = &_characterList[i]; + curAnimState = &_animator->actors()[addedObjects]; + if (ch->sceneId != _currentCharacter->sceneId) { + curAnimState->active = 0; + curAnimState->refreshFlag = 0; + curAnimState->bkgdChangeFlag = 0; + ++addedObjects; + continue; + } + + curAnimState->drawY = ch->y1; + curAnimState->sceneAnimPtr = _shapes[ch->currentAnimFrame]; + curAnimState->animFrameNumber = ch->currentAnimFrame; + startAnimFrame = ch->currentAnimFrame - 7; + xOffset = _defaultShapeTable[startAnimFrame].xOffset; + yOffset = _defaultShapeTable[startAnimFrame].yOffset; + if (_scaleMode) { + curAnimState->x1 = ch->x1; + curAnimState->y1 = ch->y1; + + _animator->_brandonScaleX = _scaleTable[ch->y1]; + _animator->_brandonScaleY = _scaleTable[ch->y1]; + + curAnimState->x1 += (_animator->_brandonScaleX * xOffset) >> 8; + curAnimState->y1 += (_animator->_brandonScaleY * yOffset) >> 8; + } else { + curAnimState->x1 = ch->x1 + xOffset; + curAnimState->y1 = ch->y1 + yOffset; + } + curAnimState->x2 = curAnimState->x1; + curAnimState->y2 = curAnimState->y1; + curAnimState->active = 1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + + if (ch->facing >= 1 && ch->facing <= 3) + curAnimState->flags |= 1; + else if (ch->facing >= 5 && ch->facing <= 7) + curAnimState->flags &= 0xFFFFFFFE; + + _animator->addObjectToQueue(curAnimState); + + ++addedObjects; + ++listAdded; + if (listAdded < 2) + i = 5; + } + + for (int i = 0; i < 11; ++i) { + curAnimState = &_animator->sprites()[i]; + + if (_sprites->_anims[i].play) { + curAnimState->active = 1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + } else { + curAnimState->active = 0; + curAnimState->refreshFlag = 0; + curAnimState->bkgdChangeFlag = 0; + } + curAnimState->height = _sprites->_anims[i].height; + curAnimState->height2 = _sprites->_anims[i].height2; + curAnimState->width = _sprites->_anims[i].width + 1; + curAnimState->width2 = _sprites->_anims[i].width2; + curAnimState->drawY = _sprites->_anims[i].drawY; + curAnimState->x1 = curAnimState->x2 = _sprites->_anims[i].x; + curAnimState->y1 = curAnimState->y2 = _sprites->_anims[i].y; + curAnimState->background = _sprites->_anims[i].background; + curAnimState->sceneAnimPtr = _sprites->_sceneShapes[_sprites->_anims[i].sprite]; + + curAnimState->disable = _sprites->_anims[i].disable; + + if (_sprites->_anims[i].unk2) + curAnimState->flags = 0x800; + else + curAnimState->flags = 0; + + if (_sprites->_anims[i].flipX) + curAnimState->flags |= 0x1; + + _animator->addObjectToQueue(curAnimState); + } + + for (int i = 0; i < 12; ++i) { + curAnimState = &_animator->items()[i]; + Room *curRoom = &_roomTable[_currentCharacter->sceneId]; + byte curItem = curRoom->itemsTable[i]; + if (curItem != 0xFF) { + curAnimState->drawY = curRoom->itemsYPos[i]; + curAnimState->sceneAnimPtr = _shapes[216 + curItem]; + curAnimState->animFrameNumber = (int16)0xFFFF; + curAnimState->y1 = curRoom->itemsYPos[i]; + curAnimState->x1 = curRoom->itemsXPos[i]; + + curAnimState->x1 -= (_animator->fetchAnimWidth(curAnimState->sceneAnimPtr, _scaleTable[curAnimState->drawY])) >> 1; + curAnimState->y1 -= _animator->fetchAnimHeight(curAnimState->sceneAnimPtr, _scaleTable[curAnimState->drawY]); + + curAnimState->x2 = curAnimState->x1; + curAnimState->y2 = curAnimState->y1; + + curAnimState->active = 1; + curAnimState->refreshFlag = 1; + curAnimState->bkgdChangeFlag = 1; + + _animator->addObjectToQueue(curAnimState); + } else { + curAnimState->active = 0; + curAnimState->refreshFlag = 0; + curAnimState->bkgdChangeFlag = 0; + } + } + + _animator->preserveAnyChangedBackgrounds(); + curAnimState = _animator->actors(); + curAnimState->bkgdChangeFlag = 1; + curAnimState->refreshFlag = 1; + for (int i = 1; i < 28; ++i) { + curAnimState = &_animator->objects()[i]; + if (curAnimState->active) { + curAnimState->bkgdChangeFlag = 1; + curAnimState->refreshFlag = 1; + } + } + _animator->restoreAllObjectBackgrounds(); + _animator->preserveAnyChangedBackgrounds(); + _animator->prepDrawAllObjects(); + initSceneScreen(brandonAlive); + _animator->copyChangedObjectsForward(0); +} + +void KyraEngine_LoK::initSceneScreen(int brandonAlive) { + if (_flags.platform == Common::kPlatformAmiga) { + if (_unkScreenVar1 && !queryGameFlag(0xF0)) { + _screen->getPalette(2).clear(); + if (_currentCharacter->sceneId != 117 || !queryGameFlag(0xB3)) + _screen->setScreenPalette(_screen->getPalette(2)); + } + + if (_unkScreenVar2 == 1) + _screen->shuffleScreen(8, 8, 304, 128, 2, 0, _unkScreenVar3, false); + else + _screen->copyRegion(8, 8, 8, 8, 304, 128, 2, 0, Screen::CR_NO_P_CHECK); + + if (_unkScreenVar1 && !queryGameFlag(0xA0)) { + if (_currentCharacter->sceneId == 45 && _cauldronState) + _screen->getPalette(0).copy(_screen->getPalette(4), 12, 1); + + if (_currentCharacter->sceneId >= 229 && _currentCharacter->sceneId <= 245 && (_brandonStatusBit & 1)) + _screen->copyPalette(0, 10); + + _screen->setScreenPalette(_screen->getPalette(0)); + } + } else { + if (_unkScreenVar1 && !queryGameFlag(0xA0)) { + for (int i = 0; i < 60; ++i) { + uint16 col = _screen->getPalette(0)[684 + i]; + col += _screen->getPalette(1)[684 + i] << 1; + col >>= 2; + _screen->getPalette(0)[684 + i] = col; + } + _screen->setScreenPalette(_screen->getPalette(0)); + } + + if (_unkScreenVar2 == 1) + _screen->shuffleScreen(8, 8, 304, 128, 2, 0, _unkScreenVar3, false); + else + _screen->copyRegion(8, 8, 8, 8, 304, 128, 2, 0); + + if (_unkScreenVar1 && _paletteChanged) { + if (!queryGameFlag(0xA0)) { + _screen->getPalette(0).copy(_screen->getPalette(1), 228, 20); + _screen->setScreenPalette(_screen->getPalette(0)); + } else { + _screen->getPalette(0).clear(); + } + } + } + + if (!_emc->start(&_scriptClick, 2)) + error("Could not start script function 2 of scene script"); + + _scriptClick.regs[7] = brandonAlive; + + while (_emc->isValid(&_scriptClick)) + _emc->run(&_scriptClick); + + setTextFadeTimerCountdown(-1); + + if (_currentCharacter->sceneId == 210) { + if (_itemInHand != kItemNone) + magicOutMouseItem(2, -1); + + _screen->hideMouse(); + for (int i = 0; i < 10; ++i) { + if (_currentCharacter->inventoryItems[i] != kItemNone) + magicOutMouseItem(2, i); + } + _screen->showMouse(); + } +} + +int KyraEngine_LoK::handleSceneChange(int xpos, int ypos, int unk1, int frameReset) { + if (queryGameFlag(0xEF)) + unk1 = 0; + + int sceneId = _currentCharacter->sceneId; + _pathfinderFlag = 0; + + if (xpos < 12) { + if (_roomTable[sceneId].westExit != 0xFFFF) { + xpos = 12; + ypos = _sceneExits.westYPos; + _pathfinderFlag = 7; + } + } else if (xpos >= 308) { + if (_roomTable[sceneId].eastExit != 0xFFFF) { + xpos = 307; + ypos = _sceneExits.eastYPos; + _pathfinderFlag = 13; + } + } + + if (ypos <= (_northExitHeight & 0xFF) + 2) { + if (_roomTable[sceneId].northExit != 0xFFFF) { + xpos = _sceneExits.northXPos; + ypos = _northExitHeight & 0xFF; + _pathfinderFlag = 14; + } + } else if (ypos >= 136) { + if (_roomTable[sceneId].southExit != 0xFFFF) { + xpos = _sceneExits.southXPos; + ypos = 136; + _pathfinderFlag = 11; + } + } + + int temp = xpos - _currentCharacter->x1; + if (ABS(temp) < 4) { + temp = ypos - _currentCharacter->y1; + if (ABS(temp) < 2) + return 0; + } + + int x = (int16)(_currentCharacter->x1 & 0xFFFC); + int y = (int16)(_currentCharacter->y1 & 0xFFFE); + xpos = (int16)(xpos & 0xFFFC); + ypos = (int16)(ypos & 0xFFFE); + + int ret = findWay(x, y, xpos, ypos, _movFacingTable, 150); + _pathfinderFlag = 0; + + if (ret >= _lastFindWayRet) + _lastFindWayRet = ret; + + if (ret == 0x7D00 || ret == 0) + return 0; + + return processSceneChange(_movFacingTable, unk1, frameReset); +} + +int KyraEngine_LoK::processSceneChange(int *table, int unk1, int frameReset) { + if (queryGameFlag(0xEF)) + unk1 = 0; + + int *tableStart = table; + _sceneChangeState = 0; + _loopFlag2 = 0; + bool running = true; + int returnValue = 0; + uint32 nextFrame = 0; + + while (running) { + bool forceContinue = false; + switch (*table) { + case 0: case 1: case 2: + case 3: case 4: case 5: + case 6: case 7: + _currentCharacter->facing = getOppositeFacingDirection(*table); + break; + + case 8: + forceContinue = true; + running = false; + break; + + default: + ++table; + forceContinue = true; + } + + returnValue = changeScene(_currentCharacter->facing); + if (returnValue) + running = false; + + if (unk1) { + if (skipFlag()) { + resetSkipFlag(false); + running = false; + _sceneChangeState = 1; + } + } + + if (forceContinue || !running) + continue; + + int temp = 0; + if (table == tableStart || table[1] == 8) + temp = setCharacterPosition(0, 0); + else + temp = setCharacterPosition(0, table); + + if (temp) + ++table; + + nextFrame = _timer->getDelay(5) * _tickLength + _system->getMillis(); + while (_system->getMillis() < nextFrame) { + _timer->update(); + + if (_currentCharacter->sceneId == 210) { + updateKyragemFading(); + if (seq_playEnd() || _beadStateVar == 4 || _beadStateVar == 5) { + *table = 8; + running = false; + break; + } + } + + if ((nextFrame - _system->getMillis()) >= 10) + delay(10, true); + } + } + + if (frameReset && !(_brandonStatusBit & 2)) + _currentCharacter->currentAnimFrame = 7; + + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); + return returnValue; +} + +int KyraEngine_LoK::changeScene(int facing) { + if (queryGameFlag(0xEF)) { + if (_currentCharacter->sceneId == 5) + return 0; + } + + int xpos = _charAddXPosTable[facing] + _currentCharacter->x1; + int ypos = _charAddYPosTable[facing] + _currentCharacter->y1; + + if (xpos >= 12 && xpos <= 308) { + if (!lineIsPassable(xpos, ypos)) + return false; + } + + if (_exitListPtr) { + int16 *ptr = _exitListPtr; + // this loop should be only entered one time, seems to be some hack in the original + while (true) { + if (*ptr == -1) + break; + + if (*ptr > _currentCharacter->x1 || _currentCharacter->y1 < ptr[1] || _currentCharacter->x1 > ptr[2] || _currentCharacter->y1 > ptr[3]) { + ptr += 10; + break; + } + + _brandonPosX = ptr[6]; + _brandonPosY = ptr[7]; + uint16 sceneId = ptr[5]; + facing = ptr[4]; + int unk1 = ptr[8]; + int unk2 = ptr[9]; + + if (sceneId == 0xFFFF) { + switch (facing) { + case 0: + sceneId = _roomTable[_currentCharacter->sceneId].northExit; + break; + + case 2: + sceneId = _roomTable[_currentCharacter->sceneId].eastExit; + break; + + case 4: + sceneId = _roomTable[_currentCharacter->sceneId].southExit; + break; + + case 6: + sceneId = _roomTable[_currentCharacter->sceneId].westExit; + break; + + default: + break; + } + } + + _currentCharacter->facing = facing; + _animator->animRefreshNPC(0); + _animator->updateAllObjectShapes(); + enterNewScene(sceneId, facing, unk1, unk2, 0); + resetGameFlag(0xEE); + return 1; + } + } + + int returnValue = 0; + facing = 0; + + if ((_northExitHeight & 0xFF) + 2 >= ypos || (_northExitHeight & 0xFF) + 2 >= _currentCharacter->y1) { + facing = 0; + returnValue = 1; + } + + if (xpos >= 308 || (_currentCharacter->x1 + 4) >= 308) { + facing = 2; + returnValue = 1; + } + + if (((_northExitHeight >> 8) & 0xFF) - 2 < ypos || ((_northExitHeight >> 8) & 0xFF) - 2 < _currentCharacter->y1) { + facing = 4; + returnValue = 1; + } + + if (xpos <= 12 || _currentCharacter->y1 <= 12) { + facing = 6; + returnValue = 1; + } + + if (!returnValue) + return 0; + + uint16 sceneId = 0xFFFF; + switch (facing) { + case 0: + sceneId = _roomTable[_currentCharacter->sceneId].northExit; + break; + + case 2: + sceneId = _roomTable[_currentCharacter->sceneId].eastExit; + break; + + case 4: + sceneId = _roomTable[_currentCharacter->sceneId].southExit; + break; + + default: + sceneId = _roomTable[_currentCharacter->sceneId].westExit; + } + + if (sceneId == 0xFFFF) + return 0; + + enterNewScene(sceneId, facing, 1, 1, 0); + return returnValue; +} + +void KyraEngine_LoK::setCharactersInDefaultScene() { + static const uint32 defaultSceneTable[][4] = { + { 0xFFFF, 0x0004, 0x0003, 0xFFFF }, + { 0xFFFF, 0x0022, 0xFFFF, 0x0000 }, + { 0xFFFF, 0x001D, 0x0021, 0xFFFF }, + { 0xFFFF, 0x0000, 0x0000, 0xFFFF } + }; + + for (int i = 1; i < 5; ++i) { + Character *cur = &_characterList[i]; + //cur->field_20 = 0; + + const uint32 *curTable = defaultSceneTable[i - 1]; + cur->sceneId = curTable[0]; + + if (cur->sceneId == _currentCharacter->sceneId) + //++cur->field_20; + cur->sceneId = curTable[1/*cur->field_20*/]; + + //cur->field_23 = curTable[cur->field_20+1]; + } +} + +void KyraEngine_LoK::setCharactersPositions(int character) { + static const uint16 initXPosTable[] = { + 0x3200, 0x0024, 0x2230, 0x2F00, 0x0020, 0x002B, + 0x00CA, 0x00F0, 0x0082, 0x00A2, 0x0042 + }; + static const uint8 initYPosTable[] = { + 0x00, 0xA2, 0x00, 0x42, 0x00, + 0x67, 0x67, 0x60, 0x5A, 0x71, + 0x76 + }; + + assert(character < ARRAYSIZE(initXPosTable)); + Character *edit = &_characterList[character]; + edit->x1 = edit->x2 = initXPosTable[character]; + edit->y1 = edit->y2 = initYPosTable[character]; +} + +#pragma mark - +#pragma mark - Pathfinder +#pragma mark - + +int KyraEngine_LoK::findWay(int x, int y, int toX, int toY, int *moveTable, int moveTableSize) { + int ret = KyraEngine_v1::findWay(x, y, toX, toY, moveTable, moveTableSize); + if (ret == 0x7D00) + return 0; + return getMoveTableSize(moveTable); +} + +bool KyraEngine_LoK::lineIsPassable(int x, int y) { + if (queryGameFlag(0xEF)) { + if (_currentCharacter->sceneId == 5) + return true; + } + + if (_pathfinderFlag & 2) { + if (x >= 312) + return false; + } + + if (_pathfinderFlag & 4) { + if (y >= 136) + return false; + } + + if (_pathfinderFlag & 8) { + if (x < 8) + return false; + } + + if (_pathfinderFlag2) { + if (x <= 8 || x >= 312) + return true; + if (y < (_northExitHeight & 0xFF) || y > 135) + return true; + } + + if (y > 137) + return false; + + if (y < 0) + y = 0; + + int ypos = 8; + if (_scaleMode) { + ypos = (_scaleTable[y] >> 5) + 1; + if (8 < ypos) + ypos = 8; + } + + x -= (ypos >> 1); + + int xpos = x; + int xtemp = xpos + ypos - 1; + if (x < 0) + xpos = 0; + + if (xtemp > 319) + xtemp = 319; + + for (; xpos < xtemp; ++xpos) { + if (!_screen->getShapeFlag1(xpos, y)) + return false; + } + return true; +} + +#pragma mark - + +void KyraEngine_LoK::setupZanthiaPalette(int pal) { + uint8 r, g, b; + + switch (pal - 17) { + case 0: + // 0x88F + r = 33; + g = 33; + b = 63; + break; + + case 1: + // 0x00F + r = 0; + g = 0; + b = 63; + break; + + case 2: + // 0xF88 + r = 63; + g = 33; + b = 33; + break; + + case 3: + // 0xF00 + r = 63; + g = 0; + b = 0; + break; + + case 4: + // 0xFF9 + r = 63; + g = 63; + b = 37; + break; + + case 5: + // 0xFF1 + r = 63; + g = 63; + b = 4; + break; + + default: + // 0xFFF + r = 63; + g = 63; + b = 63; + } + + _screen->getPalette(4)[12 * 3 + 0] = r; + _screen->getPalette(4)[12 * 3 + 1] = g; + _screen->getPalette(4)[12 * 3 + 2] = b; +} + +#pragma mark - + +void KyraEngine_LoK::setupSceneResource(int sceneId) { + if (!_flags.isTalkie) + return; + + if (_currentRoom != 0xFFFF) { + assert(_currentRoom < _roomTableSize); + int tableId = _roomTable[_currentRoom].nameIndex; + assert(tableId < _roomFilenameTableSize); + + // unload our old room + char file[64]; + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".VRM"); + _res->unloadPakFile(file); + + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".PAK"); + _res->unloadPakFile(file); + + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".APK"); + _res->unloadPakFile(file); + } + + assert(sceneId < _roomTableSize); + int tableId = _roomTable[sceneId].nameIndex; + assert(tableId < _roomFilenameTableSize); + + // load our new room + char file[64]; + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".VRM"); + if (_res->exists(file)) + _res->loadPakFile(file); + + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".PAK"); + if (_res->exists(file)) + _res->loadPakFile(file); + + strcpy(file, _roomFilenameTable[tableId]); + strcat(file, ".APK"); + if (_res->exists(file)) + _res->loadPakFile(file); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/scene_lol.cpp b/engines/kyra/engine/scene_lol.cpp new file mode 100644 index 0000000000..93ff588ece --- /dev/null +++ b/engines/kyra/engine/scene_lol.cpp @@ -0,0 +1,1577 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/engine/lol.h" +#include "kyra/graphics/screen_lol.h" +#include "kyra/resource/resource.h" +#include "kyra/engine/timer.h" + +#include "common/endian.h" +#include "common/system.h" + +namespace Kyra { + +void LoLEngine::loadLevel(int index) { + _flagsTable[73] |= 0x08; + setMouseCursorToIcon(0x85); + _nextScriptFunc = 0; + + snd_stopMusic(); + + stopPortraitSpeechAnim(); + + for (int i = 0; i < 400; i++) { + delete[] _levelDecorationShapes[i]; + _levelDecorationShapes[i] = 0; + } + _emc->unload(&_scriptData); + + resetItems(1); + disableMonsters(); + resetBlockProperties(); + + releaseMonsterShapes(0); + releaseMonsterShapes(1); + + for (int i = 0x50; i < 0x53; i++) + _timer->disable(i); + + _currentLevel = index; + _updateFlags = 0; + + setDefaultButtonState(); + + loadTalkFile(index); + + loadLevelWallData(index, true); + _loadLevelFlag = 1; + + Common::String filename = Common::String::format("LEVEL%d.INI", index); + + int f = _hasTempDataFlags & (1 << (index - 1)); + + runInitScript(filename.c_str(), f ? 0 : 1); + + if (f) + restoreBlockTempData(index); + + filename = Common::String::format("LEVEL%d.INF", index); + runInfScript(filename.c_str()); + + addLevelItems(); + deleteMonstersFromBlock(_currentBlock); + + if (!_flags.use16ColorMode) + _screen->generateGrayOverlay(_screen->getPalette(0), _screen->_grayOverlay, 32, 16, 0, 0, 128, true); + + _sceneDefaultUpdate = 0; + if (_screen->_fadeFlag == 3) + _screen->fadeToBlack(10); + + gui_drawPlayField(); + + setPaletteBrightness(_screen->getPalette(0), _brightness, _lampEffect); + setMouseCursorToItemInHand(); + + if (_flags.use16ColorMode) + _screen->fadeToPalette1(10); + + snd_playTrack(_curMusicTheme); +} + +void LoLEngine::addLevelItems() { + for (int i = 0; i < 400; i++) { + if (_itemsInPlay[i].level != _currentLevel) + continue; + + assignBlockItem(&_levelBlockProperties[_itemsInPlay[i].block], i); + + _levelBlockProperties[_itemsInPlay[i].block].direction = 5; + _itemsInPlay[i].nextDrawObject = 0; + } +} + +void LoLEngine::assignBlockItem(LevelBlockProperty *l, uint16 item) { + uint16 *index = &l->assignedObjects; + LoLObject *tmp = 0; + + while (*index & 0x8000) { + tmp = findObject(*index); + index = &tmp->nextAssignedObject; + } + + tmp = findObject(item); + ((LoLItem *)tmp)->level = -1; + + uint16 ix = *index; + + if (ix == item) + return; + + *index = item; + index = &tmp->nextAssignedObject; + + while (*index) { + tmp = findObject(*index); + index = &tmp->nextAssignedObject; + } + + *index = ix; +} + +void LoLEngine::loadLevelWallData(int index, bool mapShapes) { + Common::String filename = Common::String::format("LEVEL%d.WLL", index); + + uint32 size; + uint8 *file = _res->fileData(filename.c_str(), &size); + + uint16 c = READ_LE_UINT16(file); + loadLevelShpDat(_levelShpList[c], _levelDatList[c], false); + + uint8 *d = file + 2; + size = (size - 2) / 12; + for (uint32 i = 0; i < size; i++) { + c = READ_LE_UINT16(d); + d += 2; + _wllVmpMap[c] = *d; + d += 2; + + if (mapShapes) { + int16 sh = (int16) READ_LE_UINT16(d); + if (sh > 0) + _wllShapeMap[c] = assignLevelDecorationShapes(sh); + else + _wllShapeMap[c] = *d; + } + d += 2; + _specialWallTypes[c] = *d; + d += 2; + _wllWallFlags[c] = *d; + d += 2; + _wllAutomapData[c] = *d; + d += 2; + } + + delete[] file; + + delete _lvlShpFileHandle; + _lvlShpFileHandle = 0; +} + +int LoLEngine::assignLevelDecorationShapes(int index) { + uint16 *p1 = (uint16 *)_tempBuffer5120; + uint16 *p2 = (uint16 *)(_tempBuffer5120 + 4000); + + uint16 r = p2[index]; + if (r) + return r; + + uint16 o = _mappedDecorationsCount++; + + memcpy(&_levelDecorationProperties[o], &_levelDecorationData[index], sizeof(LevelDecorationProperty)); + + for (int i = 0; i < 10; i++) { + uint16 t = _levelDecorationProperties[o].shapeIndex[i]; + if (t == 0xFFFF) + continue; + + uint16 pv = p1[t]; + if (pv) { + _levelDecorationProperties[o].shapeIndex[i] = pv; + } else { + releaseDecorations(_lvlShapeIndex, 1); + _levelDecorationShapes[_lvlShapeIndex] = getLevelDecorationShapes(t); + p1[t] = _lvlShapeIndex; + _levelDecorationProperties[o].shapeIndex[i] = _lvlShapeIndex++; + } + } + + p2[index] = o; + if (_levelDecorationProperties[o].next) + _levelDecorationProperties[o].next = assignLevelDecorationShapes(_levelDecorationProperties[o].next); + + return o; +} + +uint8 *LoLEngine::getLevelDecorationShapes(int shapeIndex) { + if (_decorationCount <= shapeIndex) + return 0; + + _lvlShpFileHandle->seek(shapeIndex * 4 + 2, SEEK_SET); + uint32 offs = _lvlShpFileHandle->readUint32LE() + 2; + _lvlShpFileHandle->seek(offs, SEEK_SET); + + uint8 tmp[16]; + _lvlShpFileHandle->read(tmp, 16); + uint16 size = _screen->getShapeSize(tmp); + + _lvlShpFileHandle->seek(offs, SEEK_SET); + uint8 *res = new uint8[size]; + _lvlShpFileHandle->read(res, size); + + return res; +} + +void LoLEngine::releaseDecorations(int first, int num) { + for (int i = first; i < (first + num); i++) { + delete[] _levelDecorationShapes[i]; + _levelDecorationShapes[i] = 0; + } +} + +void LoLEngine::loadBlockProperties(const char *cmzFile) { + memset(_levelBlockProperties, 0, 1024 * sizeof(LevelBlockProperty)); + _screen->loadBitmap(cmzFile, 2, 2, 0); + const uint8 *h = _screen->getCPagePtr(2); + uint16 len = READ_LE_UINT16(&h[4]); + const uint8 *p = h + 6; + + for (int i = 0; i < 1024; i++) { + for (int ii = 0; ii < 4; ii++) + _levelBlockProperties[i].walls[ii] = p[i * len + ii]; + + _levelBlockProperties[i].direction = 5; + + if (_wllAutomapData[_levelBlockProperties[i].walls[0]] == 17) { + _levelBlockProperties[i].flags &= 0xEF; + _levelBlockProperties[i].flags |= 0x20; + } + } +} + +const uint8 *LoLEngine::getBlockFileData(int levelIndex) { + _screen->loadBitmap(Common::String::format("LEVEL%d.CMZ", levelIndex).c_str(), 15, 15, 0); + return _screen->getCPagePtr(14); +} + +void LoLEngine::loadLevelShpDat(const char *shpFile, const char *datFile, bool flag) { + memset(_tempBuffer5120, 0, 5120); + + _lvlShpFileHandle = _res->createReadStream(shpFile); + _decorationCount = _lvlShpFileHandle->readUint16LE(); + + Common::SeekableReadStream *s = _res->createReadStream(datFile); + + _levelDecorationDataSize = s->readUint16LE(); + delete[] _levelDecorationData; + _levelDecorationData = new LevelDecorationProperty[_levelDecorationDataSize]; + for (int i = 0; i < _levelDecorationDataSize; i++) { + LevelDecorationProperty *l = &_levelDecorationData[i]; + for (int ii = 0; ii < 10; ii++) + l->shapeIndex[ii] = s->readUint16LE(); + for (int ii = 0; ii < 10; ii++) + l->scaleFlag[ii] = s->readByte(); + for (int ii = 0; ii < 10; ii++) + l->shapeX[ii] = s->readSint16LE(); + for (int ii = 0; ii < 10; ii++) + l->shapeY[ii] = s->readSint16LE(); + l->next = s->readByte(); + l->flags = s->readByte(); + } + + delete s; + + if (!flag) { + _mappedDecorationsCount = 1; + _lvlShapeIndex = 1; + } +} + +void LoLEngine::loadLevelGraphics(const char *file, int specialColor, int weight, int vcnLen, int vmpLen, const char *palFile) { + if (file) { + _lastSpecialColor = specialColor; + _lastSpecialColorWeight = weight; + strcpy(_lastBlockDataFile, file); + if (palFile) + _lastOverridePalFile = palFile; + else + _lastOverridePalFile.clear(); + } + + if (_flags.use16ColorMode) { + if (_lastSpecialColor == 1) + _lastSpecialColor = 0x44; + else if (_lastSpecialColor == 0x66) + _lastSpecialColor = scumm_stricmp(_lastBlockDataFile, "YVEL2") ? 0xCC : 0x44; + else if (_lastSpecialColor == 0x6B) + _lastSpecialColor = 0xCC; + else + _lastSpecialColor = 0x44; + } + + Common::String fname; + const uint8 *v = 0; + int tlen = 0; + + if (_flags.use16ColorMode) { + fname = Common::String::format("%s.VCF", _lastBlockDataFile); + _screen->loadBitmap(fname.c_str(), 3, 3, 0); + v = _screen->getCPagePtr(2); + tlen = READ_LE_UINT16(v) << 5; + v += 2; + + delete[] _vcfBlocks; + _vcfBlocks = new uint8[tlen]; + + memcpy(_vcfBlocks, v, tlen); + } + + fname = Common::String::format("%s.VCN", _lastBlockDataFile); + _screen->loadBitmap(fname.c_str(), 3, 3, 0); + v = _screen->getCPagePtr(2); + tlen = READ_LE_UINT16(v); + v += 2; + + if (vcnLen == -1) + vcnLen = tlen << 5; + + delete[] _vcnBlocks; + _vcnBlocks = new uint8[vcnLen]; + + if (!_flags.use16ColorMode) { + delete[] _vcnShift; + _vcnShift = new uint8[tlen]; + + memcpy(_vcnShift, v, tlen); + v += tlen; + + memcpy(_vcnColTable, v, 128); + v += 128; + + if (!_lastOverridePalFile.empty()) { + _res->loadFileToBuf(_lastOverridePalFile.c_str(), _screen->getPalette(0).getData(), 384); + } else { + _screen->getPalette(0).copy(v, 0, 128); + } + + v += 384; + } + + if (_currentLevel == 11) { + if (_flags.use16ColorMode) { + _screen->loadPalette("LOLICE.NOL", _screen->getPalette(2)); + + } else { + _screen->loadPalette("SWAMPICE.COL", _screen->getPalette(2)); + _screen->getPalette(2).copy(_screen->getPalette(0), 128); + } + + if (_flagsTable[52] & 0x04) { + uint8 *pal0 = _screen->getPalette(0).getData(); + uint8 *pal2 = _screen->getPalette(2).getData(); + for (int i = 1; i < _screen->getPalette(0).getNumColors() * 3; i++) + SWAP(pal0[i], pal2[i]); + } + } + + memcpy(_vcnBlocks, v, vcnLen); + v += vcnLen; + + fname = Common::String::format("%s.VMP", _lastBlockDataFile); + _screen->loadBitmap(fname.c_str(), 3, 3, 0); + v = _screen->getCPagePtr(2); + + if (vmpLen == -1) + vmpLen = READ_LE_UINT16(v); + v += 2; + + delete[] _vmpPtr; + _vmpPtr = new uint16[vmpLen]; + + for (int i = 0; i < vmpLen; i++) + _vmpPtr[i] = READ_LE_UINT16(&v[i << 1]); + + Palette tpal(256); + if (_flags.use16ColorMode) { + uint8 *dst = tpal.getData(); + _res->loadFileToBuf("LOL.NOL", dst, 48); + for (int i = 1; i < 16; i++) { + int s = ((i << 4) | i) * 3; + SWAP(dst[s], dst[i * 3]); + SWAP(dst[s + 1], dst[i * 3 + 1]); + SWAP(dst[s + 2], dst[i * 3 + 2]); + } + } else { + tpal.copy(_screen->getPalette(0)); + } + + for (int i = 0; i < 7; i++) { + weight = 100 - (i * _lastSpecialColorWeight); + weight = (weight > 0) ? (weight * 255) / 100 : 0; + _screen->generateOverlay(tpal, _screen->getLevelOverlay(i), _lastSpecialColor, weight); + + int l = _flags.use16ColorMode ? 256 : 128; + uint8 *levelOverlay = _screen->getLevelOverlay(i); + for (int ii = 0; ii < l; ii++) { + if (levelOverlay[ii] == 255) + levelOverlay[ii] = 0; + } + + for (int ii = l; ii < 256; ii++) + levelOverlay[ii] = ii & 0xFF; + } + + uint8 *levelOverlay = _screen->getLevelOverlay(7); + for (int i = 0; i < 256; i++) + levelOverlay[i] = i & 0xFF; + + if (_flags.use16ColorMode) { + _screen->getLevelOverlay(6)[0xEE] = 0xEE; + if (_lastSpecialColor == 0x44) + _screen->getLevelOverlay(5)[0xEE] = 0xEE; + + for (int i = 0; i < 7; i++) + memcpy(_screen->getLevelOverlay(i), _screen->getLevelOverlay(i + 1), 256); + + _screen->loadPalette("LOL.NOL", _screen->getPalette(0)); + + for (int i = 0; i < 8; i++) { + uint8 *pl = _screen->getLevelOverlay(7 - i); + for (int ii = 0; ii < 16; ii++) + _vcnColTable[(i << 4) + ii] = pl[(ii << 4) | ii]; + } + } + + _loadSuppFilesFlag = 0; + generateBrightnessPalette(_screen->getPalette(0), _screen->getPalette(1), _brightness, _lampEffect); + + if (_flags.isTalkie) { + Common::SeekableReadStream *s = _res->createReadStream(Common::String::format("LEVEL%.02d.TLC", _currentLevel)); + s->read(_transparencyTable1, 256); + s->read(_transparencyTable2, 5120); + delete s; + } else { + createTransparencyTables(); + } + + _loadSuppFilesFlag = 1; +} + +void LoLEngine::resetItems(int flag) { + for (int i = 0; i < 1024; i++) { + _levelBlockProperties[i].direction = 5; + uint16 id = _levelBlockProperties[i].assignedObjects; + LoLObject *r = 0; + + while (id & 0x8000) { + r = findObject(id); + id = r->nextAssignedObject; + } + + if (!id) + continue; + + LoLItem *it = &_itemsInPlay[id]; + it->level = _currentLevel; + it->block = i; + if (r) + r->nextAssignedObject = 0; + } + + if (flag) + memset(_flyingObjects, 0, 8 * sizeof(FlyingObject)); +} + +void LoLEngine::disableMonsters() { + memset(_monsters, 0, 30 * sizeof(LoLMonster)); + for (int i = 0; i < 30; i++) + _monsters[i].mode = 0x10; +} + +void LoLEngine::resetBlockProperties() { + for (int i = 0; i < 1024; i++) { + LevelBlockProperty *l = &_levelBlockProperties[i]; + if (l->flags & 0x10) { + l->flags &= 0xEF; + if (testWallInvisibility(i, 0) && testWallInvisibility(i, 1)) + l->flags |= 0x40; + } else { + if (l->flags & 0x40) + l->flags &= 0xBF; + else if (l->flags & 0x80) + l->flags &= 0x7F; + } + } +} + +bool LoLEngine::testWallFlag(int block, int direction, int flag) { + if (_levelBlockProperties[block].flags & 0x10) + return true; + + if (direction != -1) + return (_wllWallFlags[_levelBlockProperties[block].walls[direction ^ 2]] & flag) ? true : false; + + for (int i = 0; i < 4; i++) { + if (_wllWallFlags[_levelBlockProperties[block].walls[i]] & flag) + return true; + } + + return false; +} + +bool LoLEngine::testWallInvisibility(int block, int direction) { + uint8 w = _levelBlockProperties[block].walls[direction]; + if (_wllVmpMap[w] || _wllShapeMap[w] || _levelBlockProperties[block].flags & 0x80) + return false; + return true; +} + +void LoLEngine::resetLampStatus() { + _flagsTable[31] |= 0x04; + _lampEffect = -1; + updateLampStatus(); +} + +void LoLEngine::setLampMode(bool lampOn) { + _flagsTable[31] &= 0xFB; + if (!(_flagsTable[31] & 0x08) || !lampOn) + return; + + _screen->drawShape(0, _gameShapes[_flags.isTalkie ? 43 : 41], 291, 56, 0, 0); + _lampEffect = 8; +} + +void LoLEngine::updateLampStatus() { + int8 newLampEffect = 0; + uint8 tmpOilStatus = 0; + + if ((_updateFlags & 4) || !(_flagsTable[31] & 0x08)) + return; + + if (!_brightness || !_lampOilStatus) { + newLampEffect = 8; + if (newLampEffect != _lampEffect && _screen->_fadeFlag == 0) + setPaletteBrightness(_screen->getPalette(0), _brightness, newLampEffect); + } else { + tmpOilStatus = (_lampOilStatus < 100) ? _lampOilStatus : 100; + newLampEffect = (3 - ((tmpOilStatus - 1) / 25)) << 1; + + if (_lampEffect == -1) { + if (_screen->_fadeFlag == 0) + setPaletteBrightness(_screen->getPalette(0), _brightness, newLampEffect); + _lampStatusTimer = _system->getMillis() + (10 + rollDice(1, 30)) * _tickLength; + } else { + if ((_lampEffect & 0xFE) == (newLampEffect & 0xFE)) { + if (_system->getMillis() <= _lampStatusTimer) { + newLampEffect = _lampEffect; + } else { + newLampEffect = _lampEffect ^ 1; + _lampStatusTimer = _system->getMillis() + (10 + rollDice(1, 30)) * _tickLength; + } + } else { + if (_screen->_fadeFlag == 0) + setPaletteBrightness(_screen->getPalette(0), _brightness, newLampEffect); + } + } + } + + if (newLampEffect == _lampEffect) + return; + + _screen->hideMouse(); + + _screen->drawShape(_screen->_curPage, _gameShapes[(_flags.isTalkie ? 35 : 33) + newLampEffect], 291, 56, 0, 0); + _screen->showMouse(); + + _lampEffect = newLampEffect; +} + +void LoLEngine::updateCompass() { + if (!(_flagsTable[31] & 0x40) || (_updateFlags & 4)) + return; + + if (_compassDirection == -1) { + _compassStep = 0; + gui_drawCompass(); + return; + } + + if (_compassTimer >= _system->getMillis()) + return; + + if ((_currentDirection << 6) == _compassDirection && (!_compassStep)) + return; + + _compassTimer = _system->getMillis() + 3 * _tickLength; + int dir = _compassStep >= 0 ? 1 : -1; + if (_compassStep) + _compassStep -= (((ABS(_compassStep) >> 4) + 2) * dir); + + int16 diff = _compassBroken ? (int8(_rnd.getRandomNumber(255)) - _compassDirection) : (_currentDirection << 6) - _compassDirection; + if (diff <= -128) + diff += 256; + if (diff >= 128) + diff -= 256; + + diff >>= 2; + _compassStep += diff; + _compassStep = CLIP(_compassStep, -24, 24); + _compassDirection += _compassStep; + + if (_compassDirection < 0) + _compassDirection += 256; + if (_compassDirection > 255) + _compassDirection -= 256; + + if (((((_compassDirection + 3) & 0xFD) >> 6) == _currentDirection) && (_compassStep < 2) && (ABS(diff) < 4)) { + _compassDirection = _currentDirection << 6; + _compassStep = 0; + } + + gui_drawCompass(); +} + +void LoLEngine::moveParty(uint16 direction, int unk1, int unk2, int buttonShape) { + if (buttonShape) + gui_toggleButtonDisplayMode(buttonShape, 1); + + uint16 opos = _currentBlock; + uint16 npos = calcNewBlockPosition(_currentBlock, direction); + + if (!checkBlockPassability(npos, direction)) { + notifyBlockNotPassable(unk2 ? 0 : 1); + gui_toggleButtonDisplayMode(buttonShape, 0); + return; + } + + _scriptDirection = direction; + _currentBlock = npos; + _sceneDefaultUpdate = 1; + + calcCoordinates(_partyPosX, _partyPosY, _currentBlock, 0x80, 0x80); + _flagsTable[73] &= 0xFD; + + runLevelScript(opos, 4); + runLevelScript(npos, 1); + + if (!(_flagsTable[73] & 0x02)) { + initTextFading(2, 0); + + if (_sceneDefaultUpdate) { + switch (unk2) { + case 0: + movePartySmoothScrollUp(2); + break; + case 1: + movePartySmoothScrollDown(2); + break; + case 2: + movePartySmoothScrollLeft(1); + break; + case 3: + movePartySmoothScrollRight(1); + break; + default: + break; + } + } else { + gui_drawScene(0); + } + + gui_toggleButtonDisplayMode(buttonShape, 0); + + if (npos == _currentBlock) { + runLevelScript(opos, 8); + runLevelScript(npos, 2); + + if (_levelBlockProperties[npos].walls[0] == 0x1A) + memset(_levelBlockProperties[npos].walls, 0, 4); + } + } + + updateAutoMap(_currentBlock); +} + +uint16 LoLEngine::calcBlockIndex(uint16 x, uint16 y) { + return (((y & 0xFF00) >> 3) | (x >> 8)) & 0x3FF; +} + +void LoLEngine::calcCoordinates(uint16 &x, uint16 &y, int block, uint16 xOffs, uint16 yOffs) { + x = (block & 0x1F) << 8 | xOffs; + y = ((block & 0xFFE0) << 3) | yOffs; +} + +void LoLEngine::calcCoordinatesForSingleCharacter(int charNum, uint16 &x, uint16 &y) { + static const uint8 xOffsets[] = { 0x80, 0x00, 0x00, 0x40, 0xC0, 0x00, 0x40, 0x80, 0xC0 }; + int c = countActiveCharacters(); + if (!c) + return; + + c = (c - 1) * 3 + charNum; + + x = xOffsets[c]; + y = 0x80; + + calcCoordinatesAddDirectionOffset(x, y, _currentDirection); + + x |= (_partyPosX & 0xFF00); + y |= (_partyPosY & 0xFF00); +} + +void LoLEngine::calcCoordinatesAddDirectionOffset(uint16 &x, uint16 &y, int direction) { + if (!direction) + return; + + int tx = x; + int ty = y; + + if (direction & 1) + SWAP(tx, ty); + + if (direction != 1) + ty = (ty - 256) * -1; + + if (direction != 3) { + tx = (tx - 256) * -1; + } + + x = tx; + y = ty; +} + +bool LoLEngine::checkBlockPassability(uint16 block, uint16 direction) { + if (testWallFlag(block, direction, 1)) + return false; + + uint16 d = _levelBlockProperties[block].assignedObjects; + + while (d) { + if (d & 0x8000) + return false; + d = findObject(d)->nextAssignedObject; + } + + return true; +} + +void LoLEngine::notifyBlockNotPassable(int scrollFlag) { + if (scrollFlag) + movePartySmoothScrollBlocked(2); + + snd_stopSpeech(true); + _txt->printMessage(0x8002, "%s", getLangString(0x403F)); + snd_playSoundEffect(19, -1); +} + +int LoLEngine::clickedDoorSwitch(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v)) + return 0; + + snd_playSoundEffect(78, -1); + _blockDoor = 0; + runLevelScript(block, 0x40); + + if (!_blockDoor) { + delay(15 * _tickLength); + processDoorSwitch(block, 0); + } + + return 1; +} + +int LoLEngine::clickedNiche(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v) || !_itemInHand) + return 0; + + uint16 x = 0x80; + uint16 y = 0xFF; + calcCoordinatesAddDirectionOffset(x, y, _currentDirection); + calcCoordinates(x, y, block, x, y); + setItemPosition(_itemInHand, x, y, 8, 1); + setHandItem(0); + return 1; +} + +void LoLEngine::movePartySmoothScrollBlocked(int speed) { + if (!_smoothScrollingEnabled || (_smoothScrollingEnabled && _needSceneRestore)) + return; + + _screen->backupSceneWindow(_sceneDrawPage2 == 2 ? 2 : 6, 6); + + for (int i = 0; i < 2; i++) { + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollZoomStepTop(6, 2, _scrollXTop[i], _scrollYTop[i]); + _screen->smoothScrollZoomStepBottom(6, 2, _scrollXBottom[i], _scrollYBottom[i]); + _screen->restoreSceneWindow(2, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + if (!_smoothScrollModeNormal) + i++; + } + + for (int i = 2; i; i--) { + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollZoomStepTop(6, 2, _scrollXTop[i], _scrollYTop[i]); + _screen->smoothScrollZoomStepBottom(6, 2, _scrollXBottom[i], _scrollYBottom[i]); + _screen->restoreSceneWindow(2, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + if (!_smoothScrollModeNormal) + i++; + } + + if (_sceneDefaultUpdate != 2) { + _screen->restoreSceneWindow(6, 0); + _screen->updateScreen(); + } + + updateDrawPage2(); +} + +void LoLEngine::movePartySmoothScrollUp(int speed) { + if (!_smoothScrollingEnabled || (_smoothScrollingEnabled && _needSceneRestore)) + return; + + int d = 0; + + if (_sceneDrawPage2 == 2) { + d = smoothScrollDrawSpecialGuiShape(6); + gui_drawScene(6); + _screen->backupSceneWindow(6, 12); + _screen->backupSceneWindow(2, 6); + } else { + d = smoothScrollDrawSpecialGuiShape(2); + gui_drawScene(2); + _screen->backupSceneWindow(2, 12); + _screen->backupSceneWindow(6, 6); + } + + for (int i = 0; i < 5; i++) { + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollZoomStepTop(6, 2, _scrollXTop[i], _scrollYTop[i]); + _screen->smoothScrollZoomStepBottom(6, 2, _scrollXBottom[i], _scrollYBottom[i]); + + if (d) + _screen->copyGuiShapeToSurface(14, 2); + + _screen->restoreSceneWindow(2, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + if (!_smoothScrollModeNormal) + i++; + } + + if (d) + _screen->copyGuiShapeToSurface(14, 12); + + if (_sceneDefaultUpdate != 2) { + _screen->restoreSceneWindow(12, 0); + _screen->updateScreen(); + } + + updateDrawPage2(); +} + +void LoLEngine::movePartySmoothScrollDown(int speed) { + if (!_smoothScrollingEnabled) + return; + + int d = smoothScrollDrawSpecialGuiShape(2); + gui_drawScene(2); + _screen->backupSceneWindow(2, 6); + + for (int i = 4; i >= 0; i--) { + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollZoomStepTop(6, 2, _scrollXTop[i], _scrollYTop[i]); + _screen->smoothScrollZoomStepBottom(6, 2, _scrollXBottom[i], _scrollYBottom[i]); + + if (d) + _screen->copyGuiShapeToSurface(14, 2); + + _screen->restoreSceneWindow(2, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + if (!_smoothScrollModeNormal) + i++; + } + + if (d) + _screen->copyGuiShapeToSurface(14, 12); + + if (_sceneDefaultUpdate != 2) { + _screen->restoreSceneWindow(6, 0); + _screen->updateScreen(); + } + + updateDrawPage2(); +} + +void LoLEngine::movePartySmoothScrollLeft(int speed) { + if (!_smoothScrollingEnabled) + return; + + speed <<= 1; + + gui_drawScene(_sceneDrawPage1); + + for (int i = 88, d = 88; i > 22; i -= 22, d += 22) { + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollHorizontalStep(_sceneDrawPage2, 66, d, i); + _screen->copyRegion(112 + i, 0, 112, 0, d, 120, _sceneDrawPage1, _sceneDrawPage2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + } + + if (_sceneDefaultUpdate != 2) { + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + SWAP(_sceneDrawPage1, _sceneDrawPage2); +} + +void LoLEngine::movePartySmoothScrollRight(int speed) { + if (!_smoothScrollingEnabled) + return; + + speed <<= 1; + + gui_drawScene(_sceneDrawPage1); + + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->copyRegion(112, 0, 222, 0, 66, 120, _sceneDrawPage1, _sceneDrawPage2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollHorizontalStep(_sceneDrawPage2, 22, 0, 66); + _screen->copyRegion(112, 0, 200, 0, 88, 120, _sceneDrawPage1, _sceneDrawPage2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollHorizontalStep(_sceneDrawPage2, 44, 0, 22); + _screen->copyRegion(112, 0, 178, 0, 110, 120, _sceneDrawPage1, _sceneDrawPage2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage2, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + if (_sceneDefaultUpdate != 2) { + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } + + SWAP(_sceneDrawPage1, _sceneDrawPage2); +} + +void LoLEngine::movePartySmoothScrollTurnLeft(int speed) { + if (!_smoothScrollingEnabled) + return; + + speed <<= 1; + + int d = smoothScrollDrawSpecialGuiShape(_sceneDrawPage1); + gui_drawScene(_sceneDrawPage1); + int dp = _sceneDrawPage2 == 2 ? _sceneDrawPage2 : _sceneDrawPage1; + + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep1(_sceneDrawPage1, _sceneDrawPage2, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep2(_sceneDrawPage1, _sceneDrawPage2, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep3(_sceneDrawPage1, _sceneDrawPage2, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + if (_sceneDefaultUpdate != 2) { + drawSpecialGuiShape(_sceneDrawPage1); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } +} + +void LoLEngine::movePartySmoothScrollTurnRight(int speed) { + if (!_smoothScrollingEnabled) + return; + + speed <<= 1; + + int d = smoothScrollDrawSpecialGuiShape(_sceneDrawPage1); + gui_drawScene(_sceneDrawPage1); + int dp = _sceneDrawPage2 == 2 ? _sceneDrawPage2 : _sceneDrawPage1; + + uint32 delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep3(_sceneDrawPage2, _sceneDrawPage1, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep2(_sceneDrawPage2, _sceneDrawPage1, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + delayTimer = _system->getMillis() + speed * _tickLength; + _screen->smoothScrollTurnStep1(_sceneDrawPage2, _sceneDrawPage1, dp); + if (d) + _screen->copyGuiShapeToSurface(14, dp); + _screen->restoreSceneWindow(dp, 0); + _screen->updateScreen(); + fadeText(); + delayUntil(delayTimer); + + if (_sceneDefaultUpdate != 2) { + drawSpecialGuiShape(_sceneDrawPage1); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + } +} + +void LoLEngine::pitDropScroll(int numSteps) { + _screen->copyRegionSpecial(0, 320, 200, 112, 0, 6, 176, 120, 0, 0, 176, 120, 0); + uint32 etime = 0; + + for (int i = 0; i < numSteps; i++) { + etime = _system->getMillis() + _tickLength; + int ys = ((30720 / numSteps) * i) >> 8; + _screen->copyRegionSpecial(6, 176, 120, 0, ys, 0, 320, 200, 112, 0, 176, 120 - ys, 0); + _screen->copyRegionSpecial(2, 320, 200, 112, 0, 0, 320, 200, 112, 120 - ys, 176, ys, 0); + _screen->updateScreen(); + + delayUntil(etime); + } + + etime = _system->getMillis() + _tickLength; + + _screen->copyRegionSpecial(2, 320, 200, 112, 0, 0, 320, 200, 112, 0, 176, 120, 0); + _screen->updateScreen(); + + delayUntil(etime); + + updateDrawPage2(); +} + +void LoLEngine::shakeScene(int duration, int width, int height, int restore) { + _screen->copyRegion(112, 0, 112, 0, 176, 120, 0, 6, Screen::CR_NO_P_CHECK); + uint32 endTime = _system->getMillis() + duration * _tickLength; + + while (endTime > _system->getMillis()) { + uint32 delayTimer = _system->getMillis() + 2 * _tickLength; + + int s1 = width ? (_rnd.getRandomNumber(255) % (width << 1)) - width : 0; + int s2 = height ? (_rnd.getRandomNumber(255) % (height << 1)) - height : 0; + + int x1, y1, x2, y2, w, h; + if (s1 >= 0) { + x1 = 112; + x2 = 112 + s1; + w = 176 - s1; + } else { + x1 = 112 - s1; + x2 = 112; + w = 176 + s1; + } + + if (s2 >= 0) { + y1 = 0; + y2 = s2; + h = 120 - s2; + } else { + y1 = -s2; + y2 = 0; + h = 120 + s2; + } + + _screen->copyRegion(x1, y1, x2, y2, w, h, 6, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + + delayUntil(delayTimer); + } + + if (restore) { + _screen->copyRegion(112, 0, 112, 0, 176, 120, 6, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + updateDrawPage2(); + } +} + +void LoLEngine::processGasExplosion(int soundId) { + int cp = _screen->setCurPage(2); + _screen->copyPage(0, 12); + + static const uint8 sounds[] = { 0x62, 0xA7, 0xA7, 0xA8 }; + snd_playSoundEffect(sounds[soundId], -1); + + uint16 targetBlock = 0; + int dist = getSpellTargetBlock(_currentBlock, _currentDirection, 3, targetBlock); + + uint8 *p1 = _screen->getPalette(1).getData(); + uint8 *p2 = _screen->getPalette(3).getData(); + + if (dist) { + WSAMovie_v2 *mov = new WSAMovie_v2(this); + Common::String file = Common::String::format("gasexp%0d.wsa", dist); + mov->open(file.c_str(), 1, 0); + if (!mov->opened()) + error("Gas: Unable to load gasexp.wsa"); + + playSpellAnimation(mov, 0, 6, 1, (176 - mov->width()) / 2 + 112, (120 - mov->height()) / 2, 0, 0, 0, 0, false); + + mov->close(); + delete mov; + + } else { + memcpy(p2, p1, 768); + + for (int i = 1; i < 128; i++) + p2[i * 3] = 0x3F; + + uint32 ctime = _system->getMillis(); + while (_screen->timedPaletteFadeStep(_screen->getPalette(0).getData(), p2, _system->getMillis() - ctime, 10)) + updateInput(); + + ctime = _system->getMillis(); + while (_screen->timedPaletteFadeStep(p2, _screen->getPalette(0).getData(), _system->getMillis() - ctime, 50)) + updateInput(); + } + + _screen->copyPage(12, 2); + _screen->setCurPage(cp); + + updateDrawPage2(); + _sceneUpdateRequired = true; + gui_drawScene(0); +} + +int LoLEngine::smoothScrollDrawSpecialGuiShape(int pageNum) { + if (!_specialGuiShape) + return 0; + + _screen->clearGuiShapeMemory(pageNum); + _screen->drawShape(pageNum, _specialGuiShape, _specialGuiShapeX, _specialGuiShapeY, 2, 0); + _screen->copyGuiShapeFromSceneBackupBuffer(pageNum, 14); + return 1; +} + +void LoLEngine::drawScene(int pageNum) { + if (pageNum && pageNum != _sceneDrawPage1) { + SWAP(_sceneDrawPage1, _sceneDrawPage2); + updateDrawPage2(); + } + + if (pageNum && pageNum != _sceneDrawPage1) { + SWAP(_sceneDrawPage1, _sceneDrawPage2); + updateDrawPage2(); + } + + generateBlockDrawingBuffer(); + drawVcnBlocks(); + drawSceneShapes(); + + if (!pageNum) { + drawSpecialGuiShape(_sceneDrawPage1); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, _sceneDrawPage2, Screen::CR_NO_P_CHECK); + _screen->copyRegion(112, 0, 112, 0, 176, 120, _sceneDrawPage1, 0, Screen::CR_NO_P_CHECK); + _screen->updateScreen(); + SWAP(_sceneDrawPage1, _sceneDrawPage2); + } + + updateEnvironmentalSfx(0); + gui_drawCompass(); + + _sceneUpdateRequired = false; +} + + +void LoLEngine::setWallType(int block, int wall, int val) { + if (wall == -1) { + for (int i = 0; i < 4; i++) + _levelBlockProperties[block].walls[i] = val; + if (_wllAutomapData[val] == 17) { + _levelBlockProperties[block].flags &= 0xEF; + _levelBlockProperties[block].flags |= 0x20; + } else { + _levelBlockProperties[block].flags &= 0xDF; + } + } else { + _levelBlockProperties[block].walls[wall] = val; + } + + checkSceneUpdateNeed(block); +} + +void LoLEngine::updateDrawPage2() { + _screen->copyRegion(112, 0, 112, 0, 176, 120, 0, _sceneDrawPage2, Screen::CR_NO_P_CHECK); +} + +void LoLEngine::prepareSpecialScene(int fieldType, int hasDialogue, int suspendGui, int allowSceneUpdate, int controlMode, int fadeFlag) { + resetPortraitsAndDisableSysTimer(); + if (fieldType) { + if (suspendGui) + gui_specialSceneSuspendControls(1); + + if (!allowSceneUpdate) + _sceneDefaultUpdate = 0; + + if (hasDialogue) + initDialogueSequence(fieldType, 0); + + if (fadeFlag) { + if (_flags.use16ColorMode) + setPaletteBrightness(_screen->getPalette(0), _brightness, _lampEffect); + else + _screen->fadePalette(_screen->getPalette(3), 10); + _screen->_fadeFlag = 0; + } + + setSpecialSceneButtons(0, 0, 320, 130, controlMode); + + } else { + if (suspendGui) + gui_specialSceneSuspendControls(0); + + if (!allowSceneUpdate) + _sceneDefaultUpdate = 0; + + gui_disableControls(controlMode); + + if (fadeFlag) { + if (_flags.use16ColorMode) { + setPaletteBrightness(_screen->getPalette(0), _brightness, _lampEffect); + } else { + _screen->getPalette(3).copy(_screen->getPalette(0), 128); + _screen->loadSpecialColors(_screen->getPalette(3)); + _screen->fadePalette(_screen->getPalette(3), 10); + } + _screen->_fadeFlag = 0; + } + + if (hasDialogue) + initDialogueSequence(fieldType, 0); + + setSpecialSceneButtons(112, 0, 176, 120, controlMode); + } +} + +int LoLEngine::restoreAfterSpecialScene(int fadeFlag, int redrawPlayField, int releaseTimScripts, int sceneUpdateMode) { + if (!_needSceneRestore) + return 0; + + _needSceneRestore = 0; + enableSysTimer(2); + + if (_dialogueField) + restoreAfterDialogueSequence(_currentControlMode); + + if (_specialSceneFlag) + gui_specialSceneRestoreControls(_currentControlMode); + + int l = _currentControlMode; + _currentControlMode = 0; + + gui_specialSceneRestoreButtons(); + calcCharPortraitXpos(); + + _currentControlMode = l; + + if (releaseTimScripts) { + for (int i = 0; i < TIM::kWSASlots; i++) + _tim->freeAnimStruct(i); + + for (int i = 0; i < 10; i++) + _tim->unload(_activeTim[i]); + } + + gui_enableControls(); + + if (fadeFlag) { + if ((_screen->_fadeFlag != 1 && _screen->_fadeFlag != 2) || (_screen->_fadeFlag == 1 && _currentControlMode)) { + if (_currentControlMode) + _screen->fadeToBlack(10); + else + _screen->fadeClearSceneWindow(10); + } + + _currentControlMode = 0; + calcCharPortraitXpos(); + + if (redrawPlayField) + gui_drawPlayField(); + + setPaletteBrightness(_screen->getPalette(0), _brightness, _lampEffect); + + } else { + _currentControlMode = 0; + calcCharPortraitXpos(); + + if (redrawPlayField) + gui_drawPlayField(); + } + + _sceneDefaultUpdate = sceneUpdateMode; + return 1; +} + +void LoLEngine::setSequenceButtons(int x, int y, int w, int h, int enableFlags) { + gui_enableSequenceButtons(x, y, w, h, enableFlags); + _seqWindowX1 = x; + _seqWindowY1 = y; + _seqWindowX2 = x + w; + _seqWindowY2 = y + h; + int offs = _itemInHand ? 10 : 0; + _screen->setMouseCursor(offs, offs, getItemIconShapePtr(_itemInHand)); + _currentFloatingCursor = -1; + if (w == 320) { + setLampMode(0); + _lampStatusSuspended = true; + } +} + +void LoLEngine::setSpecialSceneButtons(int x, int y, int w, int h, int enableFlags) { + gui_enableSequenceButtons(x, y, w, h, enableFlags); + _spsWindowX = x; + _spsWindowY = y; + _spsWindowW = w; + _spsWindowH = h; +} + +void LoLEngine::setDefaultButtonState() { + gui_enableDefaultPlayfieldButtons(); + _seqWindowX1 = _seqWindowY1 = _seqWindowX2 = _seqWindowY2 = _seqTrigger = 0; + if (_lampStatusSuspended) + resetLampStatus(); + _lampStatusSuspended = false; +} + +void LoLEngine::drawSceneShapes(int) { + for (int i = 0; i < 18; i++) { + uint8 t = _dscTileIndex[i]; + uint8 s = _visibleBlocks[t]->walls[_sceneDrawVarDown]; + + _shpDmX1 = 0; + _shpDmX2 = 0; + + int16 dimY1 = 0; + int16 dimY2 = 0; + + setLevelShapesDim(t, _shpDmX1, _shpDmX2, _sceneShpDim); + + if (_shpDmX2 <= _shpDmX1) + continue; + + drawDecorations(t); + + uint16 w = _wllWallFlags[s]; + + if (t == 16) + w |= 0x80; + + drawBlockEffects(t, 0); + + if (_visibleBlocks[t]->assignedObjects && (w & 0x80)) + drawBlockObjects(t); + + drawBlockEffects(t, 1); + + if (!(w & 8)) + continue; + + uint16 v = 20 * (s - (s < 23 ? _dscDoorScaleOffs[s] : 0)); + if (v > 80) + v = 80; + + setDoorShapeDim(t, dimY1, dimY2, _sceneShpDim); + drawDoor(_doorShapes[(s < 23 ? _dscDoorShpIndex[s] : 0)], 0, t, 10, 0, -v, 2); + setLevelShapesDim(t, dimY1, dimY2, _sceneShpDim); + } +} + +void LoLEngine::drawDecorations(int index) { + for (int i = 1; i >= 0; i--) { + int s = index * 2 + i; + uint16 scaleW = _dscShapeScaleW[s]; + uint16 scaleH = _dscShapeScaleH[s]; + int8 ix = _dscShapeIndex[s]; + uint8 shpIx = ABS(ix); + uint8 ovlIndex = _dscShapeOvlIndex[4 + _dscDimMap[index] * 5] + 2; + if (ovlIndex > 7) + ovlIndex = 7; + + if (!scaleW || !scaleH) + continue; + + uint8 d = (_currentDirection + _dscWalls[s]) & 3; + int8 l = _wllShapeMap[_visibleBlocks[index]->walls[d]]; + + uint8 *shapeData = 0; + + int x = 0; + int y = 0; + int flags = 0; + + while (l > 0) { + if ((_levelDecorationProperties[l].flags & 8) && index != 3 && index != 9 && index != 13) { + l = _levelDecorationProperties[l].next; + continue; + } + + if (_dscOvlMap[shpIx] == 1 && ((_levelDecorationProperties[l].flags & 2) || ((_levelDecorationProperties[l].flags & 4) && _wllProcessFlag))) + ix = -ix; + + int xOffs = 0; + int yOffs = 0; + uint8 *ovl = 0; + + if (_levelDecorationProperties[l].scaleFlag[shpIx] & 1) { + xOffs = _levelDecorationProperties[l].shapeX[shpIx]; + yOffs = _levelDecorationProperties[l].shapeY[shpIx]; + shpIx = _dscOvlMap[shpIx]; + int ov = ovlIndex; + if (_flags.use16ColorMode) { + uint8 bb = _blockBrightness >> 4; + if (ov > bb) + ov -= bb; + else + ov = 0; + } + ovl = _screen->getLevelOverlay(ov); + } else if (_levelDecorationProperties[l].shapeIndex[shpIx] != 0xFFFF) { + scaleW = scaleH = 0x100; + int ov = 7; + if (_flags.use16ColorMode) { + uint8 bb = _blockBrightness >> 4; + if (ov > bb) + ov -= bb; + else + ov = 0; + } + ovl = _screen->getLevelOverlay(ov); + } + + if (_levelDecorationProperties[l].shapeIndex[shpIx] != 0xFFFF) { + shapeData = _levelDecorationShapes[_levelDecorationProperties[l].shapeIndex[shpIx]]; + if (shapeData) { + if (ix < 0) { + x = _dscShapeX[s] + xOffs + ((_levelDecorationProperties[l].shapeX[shpIx] * scaleW) >> 8); + if (ix == _dscShapeIndex[s]) { + x = _dscShapeX[s] - ((_levelDecorationProperties[l].shapeX[shpIx] * scaleW) >> 8) - + _screen->getShapeScaledWidth(shapeData, scaleW) - xOffs; + } + flags = 0x105; + } else { + x = _dscShapeX[s] + xOffs + ((_levelDecorationProperties[l].shapeX[shpIx] * scaleW) >> 8); + flags = 0x104; + } + + y = _dscShapeY[s] + yOffs + ((_levelDecorationProperties[l].shapeY[shpIx] * scaleH) >> 8); + _screen->drawShape(_sceneDrawPage1, shapeData, x + 112, y, _sceneShpDim, flags, ovl, 1, scaleW, scaleH); + + if ((_levelDecorationProperties[l].flags & 1) && shpIx < 4) { + //draw shadow + x += (_screen->getShapeScaledWidth(shapeData, scaleW)); + flags ^= 1; + _screen->drawShape(_sceneDrawPage1, shapeData, x + 112, y, _sceneShpDim, flags, ovl, 1, scaleW, scaleH); + } + } + } + + l = _levelDecorationProperties[l].next; + shpIx = (_dscShapeIndex[s] < 0) ? -_dscShapeIndex[s] : _dscShapeIndex[s]; + } + } +} + +void LoLEngine::drawBlockEffects(int index, int type) { + static const uint16 yOffs[] = { 0xFF, 0xFF, 0x80, 0x80 }; + uint8 flg = _visibleBlocks[index]->flags; + // flags: 0x10 = ice wall, 0x20 = teleporter, 0x40 = blue slime spot, 0x80 = blood spot + if (!(flg & 0xF0)) + return; + + type = (type == 0) ? 2 : 0; + + for (int i = 0; i < 2; i++, type++) { + if (!((0x10 << type) & flg)) + continue; + + uint16 x = 0x80; + uint16 y = yOffs[type]; + uint16 drawFlag = (type == 3) ? 0x80 : 0x20; + uint8 *ovl = (type == 3) ? _screen->_grayOverlay : 0; + + if (_flags.use16ColorMode) { + ovl = 0; + drawFlag = (type == 0 || type == 3) ? 0 : 0x20; + } + + calcCoordinatesAddDirectionOffset(x, y, _currentDirection); + + x |= ((_visibleBlockIndex[index] & 0x1F) << 8); + y |= ((_visibleBlockIndex[index] & 0xFFE0) << 3); + + drawItemOrMonster(_effectShapes[type], ovl, x, y, 0, (type == 1) ? -20 : 0, drawFlag, -1, false); + } +} + +void LoLEngine::drawSpecialGuiShape(int pageNum) { + if (!_specialGuiShape) + return; + + _screen->drawShape(pageNum, _specialGuiShape, _specialGuiShapeX, _specialGuiShapeY, 2, 0); + + if (_specialGuiShapeMirrorFlag & 1) + _screen->drawShape(pageNum, _specialGuiShape, _specialGuiShapeX + _specialGuiShape[3], _specialGuiShapeY, 2, 1); +} + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/scene_mr.cpp b/engines/kyra/engine/scene_mr.cpp new file mode 100644 index 0000000000..8935863542 --- /dev/null +++ b/engines/kyra/engine/scene_mr.cpp @@ -0,0 +1,787 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_mr.h" +#include "kyra/graphics/screen_mr.h" +#include "kyra/sound/sound_digital.h" +#include "kyra/resource/resource.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_MR::enterNewScene(uint16 sceneId, int facing, int unk1, int unk2, int unk3) { + ++_enterNewSceneLock; + _screen->hideMouse(); + + showMessage(0, 0xF0, 0xF0); + if (_inventoryState) + hideInventory(); + + if (_currentChapter != _currentTalkFile) { + _currentTalkFile = _currentChapter; + openTalkFile(_currentTalkFile); + } + + if (unk1) { + int x = _mainCharacter.x1; + int y = _mainCharacter.y1; + + switch (facing) { + case 0: + y -= 6; + break; + + case 2: + x = 343; + break; + + case 4: + y = 191; + break; + + case 6: + x = -24; + break; + } + + moveCharacter(facing, x, y); + } + + uint32 waitUntilTimer = 0; + if (_lastMusicCommand != _sceneList[sceneId].sound) { + fadeOutMusic(60); + waitUntilTimer = _system->getMillis() + 60 * _tickLength; + } + + _chatAltFlag = false; + + if (!unk3) { + _emc->init(&_sceneScriptState, &_sceneScriptData); + _emc->start(&_sceneScriptState, 5); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + } + + _specialExitCount = 0; + Common::fill(_specialExitTable, ARRAYEND(_specialExitTable), 0xFFFF); + + _mainCharacter.sceneId = sceneId; + _sceneList[sceneId].flags &= ~1; + unloadScene(); + + for (int i = 0; i < 4; ++i) { + if (i != _musicSoundChannel && i != _fadeOutMusicChannel) + _soundDigital->stopSound(i); + } + _fadeOutMusicChannel = -1; + loadScenePal(); + + if (queryGameFlag(0x1D9)) { + char filename[20]; + if (queryGameFlag(0x20D)) { + resetGameFlag(0x20D); + strcpy(filename, "COW1_"); + } else if (queryGameFlag(0x20E)) { + resetGameFlag(0x20E); + strcpy(filename, "COW2_"); + } else if (queryGameFlag(0x20F)) { + resetGameFlag(0x20F); + strcpy(filename, "COW3_"); + } else if (queryGameFlag(0x20C)) { + resetGameFlag(0x20C); + strcpy(filename, "BOAT"); + } else if (queryGameFlag(0x210)) { + resetGameFlag(0x210); + strcpy(filename, "JUNG"); + } + + playVQA(filename); + + resetGameFlag(0x1D9); + } + + loadSceneMsc(); + _sceneExit1 = _sceneList[sceneId].exit1; + _sceneExit2 = _sceneList[sceneId].exit2; + _sceneExit3 = _sceneList[sceneId].exit3; + _sceneExit4 = _sceneList[sceneId].exit4; + + while (_system->getMillis() < waitUntilTimer) + _system->delayMillis(10); + + initSceneScript(unk3); + + if (_overwriteSceneFacing) { + facing = _mainCharacter.facing; + _overwriteSceneFacing = false; + } + + enterNewSceneUnk1(facing, unk2, unk3); + setCommandLineRestoreTimer(-1); + _sceneScriptState.regs[3] = 1; + enterNewSceneUnk2(unk3); + if (queryGameFlag(0)) { + _showOutro = true; + _runFlag = false; + } else { + if (!--_enterNewSceneLock) + _unk5 = 0; + + setNextIdleAnimTimer(); + + if (_itemInHand < 0) { + _itemInHand = kItemNone; + _mouseState = kItemNone; + _screen->setMouseCursor(0, 0, _gameShapes[0]); + } + + Common::Point pos = getMousePos(); + if (pos.y > 187) + setMousePos(pos.x, 179); + } + _screen->showMouse(); + + _currentScene = sceneId; +} + +void KyraEngine_MR::enterNewSceneUnk1(int facing, int unk1, int unk2) { + int x = 0, y = 0; + int x2 = 0, y2 = 0; + bool needProc = true; + + if (_mainCharX == -1 && _mainCharY == -1) { + switch (facing+1) { + case 1: case 2: case 8: + x2 = _sceneEnterX3; + y2 = _sceneEnterY3; + break; + + case 3: + x2 = _sceneEnterX4; + y2 = _sceneEnterY4; + break; + + case 4: case 5: case 6: + x2 = _sceneEnterX1; + y2 = _sceneEnterY1; + break; + + case 7: + x2 = _sceneEnterX2; + y2 = _sceneEnterY2; + break; + + default: + x2 = y2 = -1; + } + + if (x2 >= 316) + x2 = 312; + if (y2 >= 185) + y2 = 183; + if (x2 <= 4) + x2 = 8; + } + + if (_mainCharX >= 0) { + x = x2 = _mainCharX; + needProc = false; + } + + if (_mainCharY >= 0) { + y = y2 = _mainCharY; + needProc = false; + } + + _mainCharX = _mainCharY = -1; + + if (unk1 && needProc) { + x = x2; + y = y2; + + switch (facing) { + case 0: + y2 = 191; + break; + + case 2: + x2 = -24; + break; + + case 4: + y2 = y - 4; + break; + + case 6: + x2 = 343; + break; + + default: + break; + } + } + + x2 &= ~3; + x &= ~3; + y2 &= ~1; + y &= ~1; + + _mainCharacter.facing = facing; + _mainCharacter.x1 = _mainCharacter.x2 = x2; + _mainCharacter.y1 = _mainCharacter.y2 = y2; + initSceneAnims(unk2); + + if (_mainCharacter.sceneId == 9 && !_soundDigital->isPlaying(_musicSoundChannel)) + snd_playWanderScoreViaMap(_sceneList[_mainCharacter.sceneId].sound, 0); + if (!unk2) + snd_playWanderScoreViaMap(_sceneList[_mainCharacter.sceneId].sound, 0); + + if (unk1 && !unk2 && _mainCharacter.animFrame != 87) + moveCharacter(facing, x, y); +} + +void KyraEngine_MR::enterNewSceneUnk2(int unk1) { + _savedMouseState = -1; + if (_mainCharX == -1 && _mainCharY == -1 && !unk1) { + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); + } + + if (!unk1) { + runSceneScript4(0); + malcolmSceneStartupChat(); + } + + _unk4 = 0; + _savedMouseState = -1; +} + +void KyraEngine_MR::unloadScene() { + delete[] _sceneStrings; + _sceneStrings = 0; + _emc->unload(&_sceneScriptData); + freeSceneShapes(); + freeSceneAnims(); +} + +void KyraEngine_MR::freeSceneShapes() { + for (uint i = 0; i < ARRAYSIZE(_sceneShapes); ++i) { + delete[] _sceneShapes[i]; + _sceneShapes[i] = 0; + } +} + +void KyraEngine_MR::loadScenePal() { + char filename[16]; + _screen->copyPalette(2, 0); + strcpy(filename, _sceneList[_mainCharacter.sceneId].filename1); + strcat(filename, ".COL"); + + _screen->loadBitmap(filename, 3, 3, 0); + _screen->getPalette(2).copy(_screen->getCPagePtr(3), 0, 144); + _screen->getPalette(2).fill(0, 1, 0); + + for (int i = 144; i <= 167; ++i) { + uint8 *palette = _screen->getPalette(2).getData() + i * 3; + palette[0] = palette[2] = 63; + palette[1] = 0; + } + + _screen->generateOverlay(_screen->getPalette(2), _paletteOverlay, 0xF0, 0x19); + + _screen->getPalette(2).copy(_costPalBuffer, _characterShapeFile * 24, 24, 144); +} + +void KyraEngine_MR::loadSceneMsc() { + char filename[16]; + strcpy(filename, _sceneList[_mainCharacter.sceneId].filename1); + strcat(filename, ".MSC"); + + _res->exists(filename, true); + Common::SeekableReadStream *stream = _res->createReadStream(filename); + assert(stream); + int16 minY = 0, height = 0; + minY = stream->readSint16LE(); + height = stream->readSint16LE(); + delete stream; + stream = 0; + _maskPageMinY = minY; + _maskPageMaxY = minY + height - 1; + + _screen->setShapePages(5, 3, _maskPageMinY, _maskPageMaxY); + + _screen->loadBitmap(filename, 5, 5, 0, true); + + // HACK + uint8 *data = new uint8[320*200]; + _screen->copyRegionToBuffer(5, 0, 0, 320, 200, data); + _screen->clearPage(5); + _screen->copyBlockToPage(5, 0, _maskPageMinY, 320, height, data); + delete[] data; + +} + +void KyraEngine_MR::initSceneScript(int unk1) { + const SceneDesc &scene = _sceneList[_mainCharacter.sceneId]; + + char filename[16]; + strcpy(filename, scene.filename1); + strcat(filename, ".DAT"); + + _res->exists(filename, true); + Common::SeekableReadStream *stream = _res->createReadStream(filename); + assert(stream); + stream->seek(2, SEEK_CUR); + + byte scaleTable[15]; + stream->read(scaleTable, 15); + stream->read(_sceneDatPalette, 45); + stream->read(_sceneDatLayerTable, 15); + int16 shapesCount = stream->readSint16LE(); + + for (int i = 0; i < 15; ++i) + _scaleTable[i] = (uint16(scaleTable[i]) << 8) / 100; + + if (shapesCount > 0) { + strcpy(filename, scene.filename1); + strcat(filename, "9.CPS"); + _screen->loadBitmap(filename, 3, 3, 0); + int pageBackUp = _screen->_curPage; + _screen->_curPage = 2; + for (int i = 0; i < shapesCount; ++i) { + int16 x = stream->readSint16LE(); + int16 y = stream->readSint16LE(); + int16 w = stream->readSint16LE(); + int16 h = stream->readSint16LE(); + _sceneShapeDescs[i].drawX = stream->readSint16LE(); + _sceneShapeDescs[i].drawY = stream->readSint16LE(); + _sceneShapes[i] = _screen->encodeShape(x, y, w, h, 0); + assert(_sceneShapes[i]); + } + _screen->_curPage = pageBackUp; + } + delete stream; + stream = 0; + + strcpy(filename, scene.filename1); + strcat(filename, ".CPS"); + _screen->loadBitmap(filename, 3, 3, 0); + + Common::fill(_specialSceneScriptState, ARRAYEND(_specialSceneScriptState), false); + _sceneEnterX1 = 160; + _sceneEnterY1 = 0; + _sceneEnterX2 = 296; + _sceneEnterY2 = 93; + _sceneEnterX3 = 160; + _sceneEnterY3 = 171; + _sceneEnterX4 = 24; + _sceneEnterY4 = 93; + _sceneMinX = 0; + _sceneMaxX = 319; + + _emc->init(&_sceneScriptState, &_sceneScriptData); + strcpy(filename, scene.filename2); + strcat(filename, ".EMC"); + _res->exists(filename, true); + _emc->load(filename, &_sceneScriptData, &_opcodes); + + strcpy(filename, scene.filename2); + strcat(filename, "."); + loadLanguageFile(filename, _sceneStrings); + + runSceneScript8(); + _emc->start(&_sceneScriptState, 0); + _sceneScriptState.regs[0] = _mainCharacter.sceneId; + _sceneScriptState.regs[5] = unk1; + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + _screen->copyRegionToBuffer(3, 0, 0, 320, 200, _gamePlayBuffer); + + for (int i = 0; i < 10; ++i) { + _emc->init(&_sceneSpecialScripts[i], &_sceneScriptData); + _emc->start(&_sceneSpecialScripts[i], i+9); + _sceneSpecialScriptsTimer[i] = 0; + } + + _sceneEnterX1 &= ~3; + _sceneEnterY1 &= ~1; + _sceneEnterX2 &= ~3; + _sceneEnterY2 &= ~1; + _sceneEnterX3 &= ~3; + _sceneEnterY3 &= ~1; + _sceneEnterX4 &= ~3; + _sceneEnterY4 &= ~1; +} + +void KyraEngine_MR::initSceneAnims(int unk1) { + for (int i = 0; i < 67; ++i) + _animObjects[i].enabled = false; + + AnimObj *obj = &_animObjects[0]; + + if (_mainCharacter.animFrame != 87 && !unk1) + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + + obj->enabled = true; + obj->xPos1 = _mainCharacter.x1; + obj->yPos1 = _mainCharacter.y1; + obj->shapePtr = getShapePtr(_mainCharacter.animFrame); + obj->shapeIndex2 = obj->shapeIndex1 = _mainCharacter.animFrame; + obj->xPos2 = _mainCharacter.x1; + obj->yPos2 = _mainCharacter.y1; + _charScale = getScale(_mainCharacter.x1, _mainCharacter.y1); + obj->xPos3 = obj->xPos2 += (_malcolmShapeXOffset * _charScale) >> 8; + obj->yPos3 = obj->yPos2 += (_malcolmShapeYOffset * _charScale) >> 8; + _mainCharacter.x3 = _mainCharacter.x1 - (_charScale >> 4) - 1; + _mainCharacter.y3 = _mainCharacter.y1 - (_charScale >> 6) - 1; + obj->needRefresh = true; + _animList = 0; + + for (int i = 0; i < 16; ++i) { + const SceneAnim &anim = _sceneAnims[i]; + obj = &_animObjects[i+1]; + obj->enabled = false; + obj->needRefresh = false; + + if (anim.flags & 1) { + obj->enabled = true; + obj->needRefresh = true; + } + + obj->specialRefresh = (anim.flags & 0x20) ? 1 : 0; + obj->flags = (anim.flags & 0x10) ? 0x800 : 0; + if (anim.flags & 2) + obj->flags |= 1; + + obj->xPos1 = anim.x; + obj->yPos1 = anim.y; + + if ((anim.flags & 4) && anim.shapeIndex != -1) + obj->shapePtr = _sceneShapes[anim.shapeIndex]; + else + obj->shapePtr = 0; + + if (anim.flags & 8) { + obj->shapeIndex3 = anim.shapeIndex; + obj->animNum = i; + } else { + obj->shapeIndex3 = 0xFFFF; + obj->animNum = 0xFFFF; + } + + obj->xPos3 = obj->xPos2 = anim.x2; + obj->yPos3 = obj->yPos2 = anim.y2; + obj->width = anim.width; + obj->height = anim.height; + obj->width2 = obj->height2 = anim.specialSize; + + if (anim.flags & 1) { + if (_animList) + _animList = addToAnimListSorted(_animList, obj); + else + _animList = initAnimList(_animList, obj); + } + } + + if (_animList) + _animList = addToAnimListSorted(_animList, &_animObjects[0]); + else + _animList = initAnimList(_animList, &_animObjects[0]); + + for (int i = 0; i < 50; ++i) { + obj = &_animObjects[i+17]; + const ItemDefinition &item = _itemList[i]; + if (item.id != kItemNone && item.sceneId == _mainCharacter.sceneId) { + obj->xPos1 = item.x; + obj->yPos1 = item.y; + animSetupPaletteEntry(obj); + obj->shapePtr = 0; + obj->shapeIndex1 = obj->shapeIndex2 = item.id + 248; + obj->xPos2 = item.x; + obj->yPos2 = item.y; + + int scale = getScale(obj->xPos1, obj->yPos1); + const uint8 *shape = getShapePtr(obj->shapeIndex1); + obj->xPos3 = obj->xPos2 -= (_screen->getShapeScaledWidth(shape, scale) >> 1); + obj->yPos3 = obj->yPos2 -= _screen->getShapeScaledHeight(shape, scale) - 1; + obj->enabled = true; + obj->needRefresh = true; + + if (_animList) + _animList = addToAnimListSorted(_animList, obj); + else + _animList = initAnimList(_animList, obj); + } else { + obj->enabled = false; + obj->needRefresh = false; + } + } + + for (int i = 0; i < 67; ++i) + _animObjects[i].needRefresh = _animObjects[i].enabled; + + restorePage3(); + drawAnimObjects(); + _screen->hideMouse(); + initSceneScreen(unk1); + _screen->showMouse(); + refreshAnimObjects(0); +} + +void KyraEngine_MR::initSceneScreen(int unk1) { + _screen->copyBlockToPage(2, 0, 188, 320, 12, _interfaceCommandLine); + + if (_unkSceneScreenFlag1) { + _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK); + return; + } + + if (_noScriptEnter) { + _screen->getPalette(0).fill(0, 144, 0); + if (!_wasPlayingVQA) + _screen->setScreenPalette(_screen->getPalette(0)); + } + + _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK); + + if (_noScriptEnter) { + if (!_wasPlayingVQA) + _screen->setScreenPalette(_screen->getPalette(2)); + _screen->getPalette(0).copy(_screen->getPalette(2), 0, 144); + if (_wasPlayingVQA) { + _screen->fadeFromBlack(0x3C); + _wasPlayingVQA = false; + } + } + + updateCharPal(0); + _screen->updateScreen(); + + if (!_menuDirectlyToLoad) { + _emc->start(&_sceneScriptState, 3); + _sceneScriptState.regs[5] = unk1; + + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + } +} + +int KyraEngine_MR::trySceneChange(int *moveTable, int unk1, int updateChar) { + bool running = true; + bool unkFlag = false; + int changedScene = 0; + const int *moveTableStart = moveTable; + _unk4 = 0; + + while (running && !shouldQuit()) { + if (*moveTable >= 0 && *moveTable <= 7) { + _mainCharacter.facing = getOppositeFacingDirection(*moveTable); + unkFlag = true; + } else { + if (*moveTable == 8) { + running = false; + } else { + ++moveTable; + unkFlag = false; + } + } + + if (checkSceneChange()) { + running = false; + changedScene = 1; + } + + if (unk1) { + // Notice that we can't use KyraEngine_MR's skipFlag handling + // here, since Kyra3 allows disabling of skipFlag support + if (KyraEngine_v2::skipFlag()) { + resetSkipFlag(false); + running = false; + _unk4 = 1; + } + } + + if (!unkFlag || !running) + continue; + + int ret = 0; + if (moveTable == moveTableStart || moveTable[1] == 8) + ret = updateCharPos(0, 0); + else + ret = updateCharPos(moveTable, 0); + + if (ret) + ++moveTable; + + delay(10, true); + } + + if (updateChar) + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); + + return changedScene; +} + +int KyraEngine_MR::checkSceneChange() { + const SceneDesc &curScene = _sceneList[_mainCharacter.sceneId]; + int charX = _mainCharacter.x1, charY = _mainCharacter.y1; + int facing = 0; + int process = 0; + + if (_screen->getLayer(charX, charY) == 1 && _savedMouseState == -7) { + facing = 0; + process = 1; + } else if (charX >= 316 && _savedMouseState == -6) { + facing = 2; + process = 1; + } else if (charY >= 186 && _savedMouseState == -5) { + facing = 4; + process = 1; + } else if (charX <= 4 && _savedMouseState == -4) { + facing = 6; + process = 1; + } + + if (!process) + return 0; + + uint16 newScene = 0xFFFF; + switch (facing) { + case 0: + newScene = curScene.exit1; + break; + + case 2: + newScene = curScene.exit2; + break; + + case 4: + newScene = curScene.exit3; + break; + + case 6: + newScene = curScene.exit4; + break; + + default: + newScene = _mainCharacter.sceneId; + } + + if (newScene == 0xFFFF) + return 0; + + enterNewScene(newScene, facing, 1, 1, 0); + return 1; +} +int KyraEngine_MR::runSceneScript1(int x, int y) { + if (y > 187 && _savedMouseState > -4) + return 0; + if (_deathHandler >= 0) + return 0; + + _emc->init(&_sceneScriptState, &_sceneScriptData); + _sceneScriptState.regs[1] = x; + _sceneScriptState.regs[2] = y; + _sceneScriptState.regs[3] = 0; + _sceneScriptState.regs[4] = _itemInHand; + + _emc->start(&_sceneScriptState, 1); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + return _sceneScriptState.regs[3]; +} + +int KyraEngine_MR::runSceneScript2() { + _sceneScriptState.regs[1] = _mouseX; + _sceneScriptState.regs[2] = _mouseY; + _sceneScriptState.regs[3] = 0; + _sceneScriptState.regs[4] = _itemInHand; + + _emc->start(&_sceneScriptState, 2); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + return _sceneScriptState.regs[3]; +} + +void KyraEngine_MR::runSceneScript4(int unk1) { + _sceneScriptState.regs[4] = _itemInHand; + _sceneScriptState.regs[5] = unk1; + _sceneScriptState.regs[3] = 0; + _noStartupChat = false; + + _emc->start(&_sceneScriptState, 4); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); + + if (_sceneScriptState.regs[3]) + _noStartupChat = true; +} + +void KyraEngine_MR::runSceneScript8() { + _emc->start(&_sceneScriptState, 8); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +bool KyraEngine_MR::lineIsPassable(int x, int y) { + static const uint8 widthTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 }; + + if ((_pathfinderFlag & 2) && x >= 320) + return false; + if ((_pathfinderFlag & 4) && y >= 188) + return false; + if ((_pathfinderFlag & 8) && x < 0) + return false; + if (y > 187) + return false; + + uint width = widthTable[getScale(x, y) >> 5]; + + if (y < 0) + y = 0; + x -= width >> 1; + if (x < 0) + x = 0; + int x2 = x + width; + if (x2 > 320) + x2 = 320; + + for (; x < x2; ++x) { + if (y < _maskPageMinY || y > _maskPageMaxY) + return false; + + if (!_screen->getShapeFlag1(x, y)) + return false; + } + + return true; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/scene_rpg.cpp b/engines/kyra/engine/scene_rpg.cpp new file mode 100644 index 0000000000..72922d4b53 --- /dev/null +++ b/engines/kyra/engine/scene_rpg.cpp @@ -0,0 +1,658 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) + +#include "kyra/engine/kyra_rpg.h" +#include "kyra/resource/resource.h" +#include "kyra/engine/timer.h" +#include "kyra/sound/sound.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraRpgEngine::setLevelShapesDim(int index, int16 &x1, int16 &x2, int dim) { + if (_lvlShapeLeftRight[index << 1] == -1) { + x1 = 0; + x2 = 22; + + int16 y1 = 0; + int16 y2 = 120; + + int m = index * 18; + + for (int i = 0; i < 18; i++) { + uint8 d = _visibleBlocks[i]->walls[_sceneDrawVarDown]; + uint8 a = _wllWallFlags[d]; + + if (a & 8) { + int t = _dscDim2[(m + i) << 1]; + + if (t > x1) { + x1 = t; + if (!(a & 0x10)) + setDoorShapeDim(index, y1, y2, -1); + } + + t = _dscDim2[((m + i) << 1) + 1]; + + if (t < x2) { + x2 = t; + if (!(a & 0x10)) + setDoorShapeDim(index, y1, y2, -1); + } + } else { + int t = _dscDim1[m + i]; + + if (!_wllVmpMap[d] || t == -40) + continue; + + if (t == -41) { + x1 = 22; + x2 = 0; + break; + } + + if (t > 0 && x2 > t) + x2 = t; + + if (t < 0 && x1 < -t) + x1 = -t; + } + + if (x2 < x1) + break; + } + + x1 += (_sceneXoffset >> 3); + x2 += (_sceneXoffset >> 3); + + + _lvlShapeTop[index] = y1; + _lvlShapeBottom[index] = y2; + _lvlShapeLeftRight[index << 1] = x1; + _lvlShapeLeftRight[(index << 1) + 1] = x2; + } else { + x1 = _lvlShapeLeftRight[index << 1]; + x2 = _lvlShapeLeftRight[(index << 1) + 1]; + } + + drawLevelModifyScreenDim(dim, x1, 0, x2, 15); +} + +void KyraRpgEngine::setDoorShapeDim(int index, int16 &y1, int16 &y2, int dim) { + uint8 a = _dscDimMap[index]; + + if (_flags.gameID != GI_EOB1 && dim == -1 && a != 3) + a++; + + uint8 b = a; + if (_flags.gameID == GI_EOB1) { + a += _dscDoorFrameIndex1[_currentLevel - 1]; + b += _dscDoorFrameIndex2[_currentLevel - 1]; + } + + y1 = _dscDoorFrameY1[a]; + y2 = _dscDoorFrameY2[b]; + + if (dim == -1) + return; + + const ScreenDim *cDim = screen()->getScreenDim(dim); + + screen()->modifyScreenDim(dim, cDim->sx, y1, cDim->w, y2 - y1); +} + +void KyraRpgEngine::drawLevelModifyScreenDim(int dim, int16 x1, int16 y1, int16 x2, int16 y2) { + screen()->modifyScreenDim(dim, x1, y1 << 3, x2 - x1, (y2 - y1) << 3); +} + +void KyraRpgEngine::generateBlockDrawingBuffer() { + _sceneDrawVarDown = _dscBlockMap[_currentDirection]; + _sceneDrawVarRight = _dscBlockMap[_currentDirection + 4]; + _sceneDrawVarLeft = _dscBlockMap[_currentDirection + 8]; + + /******************************************* + * _visibleBlocks map * + * * + * | | | | | | * + * 00 | 01 | 02 | 03 | 04 | 05 | 06 * + * ____|_____|_____|_____|_____|_____|_____ * + * | | | | | | * + * | 07 | 08 | 09 | 10 | 11 | * + * |_____|_____|_____|_____|_____| * + * | | | | * + * | 12 | 13 | 14 | * + * |_____|_____|_____| * + * | | * + * 15 | 16 | 17 * + * | (P) | * + ********************************************/ + + memset(_blockDrawingBuffer, 0, 660 * sizeof(uint16)); + + _wllProcessFlag = ((_currentBlock >> 5) + (_currentBlock & 0x1F) + _currentDirection) & 1; + + if (_wllProcessFlag) // floor and ceiling + generateVmpTileDataFlipped(0, 15, 1, -330, 22, 15); + else + generateVmpTileData(0, 15, 1, -330, 22, 15); + + assignVisibleBlocks(_currentBlock, _currentDirection); + + uint8 t = _visibleBlocks[0]->walls[_sceneDrawVarRight]; + if (t) + generateVmpTileData(-2, 3, t, 102, 3, 5); + + t = _visibleBlocks[6]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileDataFlipped(21, 3, t, 102, 3, 5); + + t = _visibleBlocks[1]->walls[_sceneDrawVarRight]; + uint8 t2 = _visibleBlocks[2]->walls[_sceneDrawVarDown]; + + if (hasWall(t) && !(_wllWallFlags[t2] & 8)) + generateVmpTileData(2, 3, t, 102, 3, 5); + else if (t && (_wllWallFlags[t2] & 8)) + generateVmpTileData(2, 3, t2, 102, 3, 5); + + t = _visibleBlocks[5]->walls[_sceneDrawVarLeft]; + t2 = _visibleBlocks[4]->walls[_sceneDrawVarDown]; + + if (hasWall(t) && !(_wllWallFlags[t2] & 8)) + generateVmpTileDataFlipped(17, 3, t, 102, 3, 5); + else if (t && (_wllWallFlags[t2] & 8)) + generateVmpTileDataFlipped(17, 3, t2, 102, 3, 5); + + t = _visibleBlocks[2]->walls[_sceneDrawVarRight]; + if (t) + generateVmpTileData(8, 3, t, 97, 1, 5); + + t = _visibleBlocks[4]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileDataFlipped(13, 3, t, 97, 1, 5); + + t = _visibleBlocks[1]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(-4, 3, t, 129, 6, 5); + + t = _visibleBlocks[5]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(20, 3, t, 129, 6, 5); + + t = _visibleBlocks[2]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(2, 3, t, 129, 6, 5); + + t = _visibleBlocks[4]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(14, 3, t, 129, 6, 5); + + t = _visibleBlocks[3]->walls[_sceneDrawVarDown]; + if (t) + generateVmpTileData(8, 3, t, 129, 6, 5); + + t = _visibleBlocks[7]->walls[_sceneDrawVarRight]; + if (t) + generateVmpTileData(0, 3, t, 117, 2, 6); + + t = _visibleBlocks[11]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileDataFlipped(20, 3, t, 117, 2, 6); + + t = _visibleBlocks[8]->walls[_sceneDrawVarRight]; + if (t) + generateVmpTileData(6, 2, t, 81, 2, 8); + + t = _visibleBlocks[10]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileDataFlipped(14, 2, t, 81, 2, 8); + + t = _visibleBlocks[8]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(-4, 2, t, 159, 10, 8); + + t = _visibleBlocks[10]->walls[_sceneDrawVarDown]; + if (hasWall(t)) + generateVmpTileData(16, 2, t, 159, 10, 8); + + t = _visibleBlocks[9]->walls[_sceneDrawVarDown]; + if (t) + generateVmpTileData(6, 2, t, 159, 10, 8); + + t = _visibleBlocks[12]->walls[_sceneDrawVarRight]; + if (t) + generateVmpTileData(3, 1, t, 45, 3, 12); + + t = _visibleBlocks[14]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileDataFlipped(16, 1, t, 45, 3, 12); + + t = _visibleBlocks[12]->walls[_sceneDrawVarDown]; + if (!(_wllWallFlags[t] & 8)) + generateVmpTileData(-13, 1, t, 239, 16, 12); + + t = _visibleBlocks[14]->walls[_sceneDrawVarDown]; + if (!(_wllWallFlags[t] & 8)) + generateVmpTileData(19, 1, t, 239, 16, 12); + + t = _visibleBlocks[13]->walls[_sceneDrawVarDown]; + if (t) + generateVmpTileData(3, 1, t, 239, 16, 12); + + t = _visibleBlocks[15]->walls[_sceneDrawVarRight]; + t2 = _visibleBlocks[17]->walls[_sceneDrawVarLeft]; + if (t) + generateVmpTileData(0, 0, t, 0, 3, 15); + if (t2) + generateVmpTileDataFlipped(19, 0, t2, 0, 3, 15); +} + +void KyraRpgEngine::generateVmpTileData(int16 startBlockX, uint8 startBlockY, uint8 vmpMapIndex, int16 vmpOffset, uint8 numBlocksX, uint8 numBlocksY) { + if (!_wllVmpMap[vmpMapIndex]) + return; + + uint16 *vmp = &_vmpPtr[(_wllVmpMap[vmpMapIndex] - 1) * 431 + vmpOffset + 330]; + + for (int i = 0; i < numBlocksY; i++) { + uint16 *bl = &_blockDrawingBuffer[(startBlockY + i) * 22 + startBlockX]; + for (int ii = 0; ii < numBlocksX; ii++) { + if ((startBlockX + ii >= 0) && (startBlockX + ii < 22) && *vmp) + *bl = *vmp; + bl++; + vmp++; + } + } +} + +void KyraRpgEngine::generateVmpTileDataFlipped(int16 startBlockX, uint8 startBlockY, uint8 vmpMapIndex, int16 vmpOffset, uint8 numBlocksX, uint8 numBlocksY) { + if (!_wllVmpMap[vmpMapIndex]) + return; + + uint16 *vmp = &_vmpPtr[(_wllVmpMap[vmpMapIndex] - 1) * 431 + vmpOffset + 330]; + + for (int i = 0; i < numBlocksY; i++) { + for (int ii = 0; ii < numBlocksX; ii++) { + if ((startBlockX + ii) < 0 || (startBlockX + ii) > 21) + continue; + + uint16 v = vmp[i * numBlocksX + (numBlocksX - 1 - ii)]; + if (!v) + continue; + + if (v & 0x4000) + v -= 0x4000; + else + v |= 0x4000; + + _blockDrawingBuffer[(startBlockY + i) * 22 + startBlockX + ii] = v; + } + } +} + +bool KyraRpgEngine::hasWall(int index) { + if (!index || (_wllWallFlags[index] & 8)) + return false; + return true; +} + +void KyraRpgEngine::assignVisibleBlocks(int block, int direction) { + for (int i = 0; i < 18; i++) { + uint16 t = (block + _dscBlockIndex[direction * 18 + i]) & 0x3FF; + _visibleBlockIndex[i] = t; + + _visibleBlocks[i] = &_levelBlockProperties[t]; + _lvlShapeLeftRight[i] = _lvlShapeLeftRight[18 + i] = -1; + } +} + +bool KyraRpgEngine::checkSceneUpdateNeed(int block) { + if (_sceneUpdateRequired) + return true; + + for (int i = 0; i < 15; i++) { + if (_visibleBlockIndex[i] == block) { + _sceneUpdateRequired = true; + return true; + } + } + + if (_currentBlock == block) { + _sceneUpdateRequired = true; + return true; + } + + return false; +} + +void KyraRpgEngine::drawVcnBlocks() { + uint8 *d = _sceneWindowBuffer; + uint16 *bdb = _blockDrawingBuffer; + uint16 *hiColorPal = screen()->get16bitPalette(); + + for (int y = 0; y < 15; y++) { + for (int x = 0; x < 22; x++) { + bool horizontalFlip = false; + uint16 vcnOffset = *bdb++; + uint16 vcnExtraOffsetWll = 0; + int wllVcnOffset = 0; + int wllVcnRmdOffset = 0; + + if (vcnOffset & 0x8000) { + // this renders a wall block over the transparent pixels of a floor/ceiling block + vcnExtraOffsetWll = vcnOffset - 0x8000; + vcnOffset = 0; + wllVcnRmdOffset = _wllVcnOffset; + } + + if (vcnOffset & 0x4000) { + horizontalFlip = true; + vcnOffset &= 0x3FFF; + } + + const uint8 *src = 0; + if (vcnOffset) { + src = &_vcnBlocks[vcnOffset << (4 + _vcnBpp)]; + wllVcnOffset = _wllVcnOffset; + } else { + // floor/ceiling blocks + vcnOffset = bdb[329]; + if (vcnOffset & 0x4000) { + horizontalFlip = true; + vcnOffset &= 0x3FFF; + } + + src = (_vcfBlocks ? _vcfBlocks : _vcnBlocks) + (vcnOffset << (4 + _vcnBpp)); + } + + uint8 shift = _vcnShift ? _vcnShift[vcnOffset] : _blockBrightness; + + if (horizontalFlip) { + for (int blockY = 0; blockY < 8; blockY++) { + src += ((_vcnBpp << 2) - 1); + for (int blockX = 0; blockX < 4 * _vcnBpp; blockX++) { + if (_vcnBpp == 2) { + *(uint16*)d = hiColorPal[*src--]; + d += 2; + } else { + uint8 bl = *src--; + *d++ = _vcnColTable[((bl & 0x0F) + wllVcnOffset) | shift]; + *d++ = _vcnColTable[((bl >> 4) + wllVcnOffset) | shift]; + } + } + src += ((_vcnBpp << 2) + 1); + d += 168 * _vcnBpp; + } + } else { + for (int blockY = 0; blockY < 8; blockY++) { + for (int blockX = 0; blockX < 4 * _vcnBpp; blockX++) { + if (_vcnBpp == 2) { + *(uint16*)d = hiColorPal[*src++]; + d += 2; + } else { + uint8 bl = *src++; + *d++ = _vcnColTable[((bl >> 4) + wllVcnOffset) | shift]; + *d++ = _vcnColTable[((bl & 0x0F) + wllVcnOffset) | shift]; + } + } + d += 168 * _vcnBpp; + } + } + d -= 1400 * _vcnBpp; + + if (vcnExtraOffsetWll) { + d -= 8 * _vcnBpp; + horizontalFlip = false; + + if (vcnExtraOffsetWll & 0x4000) { + vcnExtraOffsetWll &= 0x3FFF; + horizontalFlip = true; + } + + shift = _vcnShift ? _vcnShift[vcnExtraOffsetWll] : _blockBrightness; + src = &_vcnBlocks[vcnExtraOffsetWll << (4 + _vcnBpp)]; + uint8 *maskTable = _vcnTransitionMask ? &_vcnTransitionMask[vcnExtraOffsetWll << 5] : 0; + + if (horizontalFlip) { + for (int blockY = 0; blockY < 8; blockY++) { + src += ((_vcnBpp << 2) - 1); + maskTable += 3; + for (int blockX = 0; blockX < 4 * _vcnBpp; blockX++) { + if (_vcnBpp == 2) { + uint8 bl = *src--; + if (bl) + *(uint16*)d = hiColorPal[bl]; + d += 2; + } else { + uint8 bl = *src--; + uint8 mask = _vcnTransitionMask ? *maskTable-- : 0; + uint8 h = _vcnColTable[((bl & 0x0F) + wllVcnRmdOffset) | shift]; + uint8 l = _vcnColTable[((bl >> 4) + wllVcnRmdOffset) | shift]; + + if (_vcnTransitionMask) + *d = (*d & (mask & 0x0F)) | h; + else if (h) + *d = h; + d++; + + if (_vcnTransitionMask) + *d = (*d & (mask >> 4)) | l; + else if (l) + *d = l; + d++; + } + } + src += ((_vcnBpp << 2) + 1); + maskTable += 5; + d += 168 * _vcnBpp; + } + } else { + for (int blockY = 0; blockY < 8; blockY++) { + for (int blockX = 0; blockX < 4 * _vcnBpp; blockX++) { + if (_vcnBpp == 2) { + uint8 bl = *src++; + if (bl) + *(uint16*)d = hiColorPal[bl]; + d += 2; + } else { + uint8 bl = *src++; + uint8 mask = _vcnTransitionMask ? *maskTable++ : 0; + uint8 h = _vcnColTable[((bl >> 4) + wllVcnRmdOffset) | shift]; + uint8 l = _vcnColTable[((bl & 0x0F) + wllVcnRmdOffset) | shift]; + + if (_vcnTransitionMask) + *d = (*d & (mask >> 4)) | h; + else if (h) + *d = h; + d++; + + if (_vcnTransitionMask) + *d = (*d & (mask & 0x0F)) | l; + else if (l) + *d = l; + d++; + } + } + d += 168 * _vcnBpp; + } + } + d -= 1400 * _vcnBpp; + } + } + d += 1232 * _vcnBpp; + } + + screen()->copyBlockToPage(_sceneDrawPage1, _sceneXoffset, 0, 176, 120, _sceneWindowBuffer); +} + +uint16 KyraRpgEngine::calcNewBlockPosition(uint16 curBlock, uint16 direction) { + static const int16 blockPosTable[] = { -32, 1, 32, -1 }; + return (curBlock + blockPosTable[direction]) & 0x3FF; +} + +int KyraRpgEngine::clickedWallShape(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v)) + return 0; + + snd_stopSpeech(true); + runLevelScript(block, 0x40); + + return 1; +} + +int KyraRpgEngine::clickedLeverOn(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v)) + return 0; + + _levelBlockProperties[block].walls[direction]++; + _sceneUpdateRequired = true; + + if (_flags.gameID == GI_LOL) + snd_playSoundEffect(30, -1); + + runLevelScript(block, _clickedSpecialFlag); + + return 1; +} + +int KyraRpgEngine::clickedLeverOff(uint16 block, uint16 direction) { + uint8 v = _wllShapeMap[_levelBlockProperties[block].walls[direction]]; + if (!clickedShape(v)) + return 0; + + _levelBlockProperties[block].walls[direction]--; + _sceneUpdateRequired = true; + + if (_flags.gameID == GI_LOL) + snd_playSoundEffect(29, -1); + + runLevelScript(block, _clickedSpecialFlag); + return 1; +} + +int KyraRpgEngine::clickedWallOnlyScript(uint16 block) { + runLevelScript(block, _clickedSpecialFlag); + return 1; +} + +void KyraRpgEngine::processDoorSwitch(uint16 block, int openClose) { + if (block == _currentBlock) + return; + + if ((_flags.gameID == GI_LOL && (_levelBlockProperties[block].assignedObjects & 0x8000)) || (_flags.gameID != GI_LOL && (_levelBlockProperties[block].flags & 7))) + return; + + if (openClose == 0) { + for (int i = 0; i < 3; i++) { + if (_openDoorState[i].block != block) + continue; + openClose = -_openDoorState[i].state; + break; + } + } + + if (openClose == 0) { + openClose = (_wllWallFlags[_levelBlockProperties[block].walls[_wllWallFlags[_levelBlockProperties[block].walls[0]] & 8 ? 0 : 1]] & 1) ? 1 : -1; + if (_flags.gameID != GI_LOL) + openClose *= -1; + } + + openCloseDoor(block, openClose); +} + +void KyraRpgEngine::openCloseDoor(int block, int openClose) { + int s1 = -1; + int s2 = -1; + + int c = (_wllWallFlags[_levelBlockProperties[block].walls[0]] & 8) ? 0 : 1; + int v = _levelBlockProperties[block].walls[c]; + int flg = (_flags.gameID == GI_EOB1) ? 1 : ((openClose == 1) ? 0x10 : (openClose == -1 ? 0x20 : 0)); + + if ((_flags.gameID == GI_EOB1 && openClose == -1 && !(_wllWallFlags[v] & flg)) || (!(_flags.gameID == GI_EOB1 && openClose == -1) && (_wllWallFlags[v] & flg))) + return; + + for (int i = 0; i < 3; i++) { + if (_openDoorState[i].block == block) { + s1 = i; + break; + } else if (_openDoorState[i].block == 0 && s2 == -1) { + s2 = i; + } + } + + if (s1 != -1 || s2 != -1) { + if (s1 == -1) + s1 = s2; + + _openDoorState[s1].block = block; + _openDoorState[s1].state = openClose; + _openDoorState[s1].wall = c; + + flg = (-openClose == 1) ? 0x10 : (-openClose == -1 ? 0x20 : 0); + + if (_wllWallFlags[v] & flg) { + _levelBlockProperties[block].walls[c] += openClose; + _levelBlockProperties[block].walls[c ^ 2] += openClose; + + int snd = (openClose == -1) ? 4 : 3; + if (_flags.gameID == GI_LOL) { + snd_processEnvironmentalSoundEffect(snd + 28, _currentBlock); + if (!checkSceneUpdateNeed(block)) + updateEnvironmentalSfx(0); + } else { + updateEnvironmentalSfx(snd); + } + } + + enableTimer(_flags.gameID == GI_LOL ? 0 : 4); + + } else { + while (!(flg & _wllWallFlags[v])) + v += openClose; + + _levelBlockProperties[block].walls[c] = _levelBlockProperties[block].walls[c ^ 2] = v; + checkSceneUpdateNeed(block); + } +} + +void KyraRpgEngine::completeDoorOperations() { + for (int i = 0; i < 3; i++) { + if (!_openDoorState[i].block) + continue; + + uint16 b = _openDoorState[i].block; + + do { + _levelBlockProperties[b].walls[_openDoorState[i].wall] += _openDoorState[i].state; + _levelBlockProperties[b].walls[_openDoorState[i].wall ^ 2] += _openDoorState[i].state; + } while (!(_wllWallFlags[_levelBlockProperties[b].walls[_openDoorState[i].wall]] & 0x30)); + + _openDoorState[i].block = 0; + } +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB || ENABLE_LOL diff --git a/engines/kyra/engine/scene_v1.cpp b/engines/kyra/engine/scene_v1.cpp new file mode 100644 index 0000000000..48958e5b90 --- /dev/null +++ b/engines/kyra/engine/scene_v1.cpp @@ -0,0 +1,360 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/kyra_v1.h" + +namespace Kyra { + +int KyraEngine_v1::findWay(int x, int y, int toX, int toY, int *moveTable, int moveTableSize) { + x &= 0xFFFC; toX &= 0xFFFC; + y &= 0xFFFE; toY &= 0xFFFE; + x = (int16)x; y = (int16)y; toX = (int16)toX; toY = (int16)toY; + + if (x == toY && y == toY) { + moveTable[0] = 8; + return 0; + } + + int curX = x; + int curY = y; + int tempValue = 0; + int lastUsedEntry = 0; + int *pathTable1 = new int[0x7D0]; + int *pathTable2 = new int[0x7D0]; + assert(pathTable1 && pathTable2); + + while (true) { + int newFacing = getFacingFromPointToPoint(x, y, toX, toY); + changePosTowardsFacing(curX, curY, newFacing); + + if (curX == toX && curY == toY) { + if (!lineIsPassable(curX, curY)) + break; + moveTable[lastUsedEntry++] = newFacing; + break; + } + + if (lineIsPassable(curX, curY)) { + if (lastUsedEntry == moveTableSize) { + delete[] pathTable1; + delete[] pathTable2; + return 0x7D00; + } + // debug drawing + /*if (curX >= 0 && curY >= 0 && curX < 320 && curY < 200) { + screen()->setPagePixel(0, curX, curY, 11); + screen()->updateScreen(); + delayWithTicks(5); + }*/ + moveTable[lastUsedEntry++] = newFacing; + x = curX; + y = curY; + continue; + } + + int temp = 0; + while (true) { + newFacing = getFacingFromPointToPoint(curX, curY, toX, toY); + changePosTowardsFacing(curX, curY, newFacing); + // debug drawing + /*if (curX >= 0 && curY >= 0 && curX < 320 && curY < 200) { + screen()->setPagePixel(0, curX, curY, 8); + screen()->updateScreen(); + delayWithTicks(5); + }*/ + + if (!lineIsPassable(curX, curY)) { + if (curX != toX || curY != toY) + continue; + } + + if (curX == toX && curY == toY) { + if (!lineIsPassable(curX, curY)) { + tempValue = 0; + temp = 0; + break; + } + } + + temp = findSubPath(x, y, curX, curY, pathTable1, 1, 0x7D0); + tempValue = findSubPath(x, y, curX, curY, pathTable2, 0, 0x7D0); + if (curX == toX && curY == toY) { + if (temp == 0x7D00 && tempValue == 0x7D00) { + delete[] pathTable1; + delete[] pathTable2; + return 0x7D00; + } + } + + if (temp != 0x7D00 || tempValue != 0x7D00) + break; + } + + if (temp < tempValue) { + if (lastUsedEntry + temp > moveTableSize) { + delete[] pathTable1; + delete[] pathTable2; + return 0x7D00; + } + memcpy(&moveTable[lastUsedEntry], pathTable1, temp * sizeof(int)); + lastUsedEntry += temp; + } else { + if (lastUsedEntry + tempValue > moveTableSize) { + delete[] pathTable1; + delete[] pathTable2; + return 0x7D00; + } + memcpy(&moveTable[lastUsedEntry], pathTable2, tempValue * sizeof(int)); + lastUsedEntry += tempValue; + } + x = curX; + y = curY; + if (curX == toX && curY == toY) + break; + } + + delete[] pathTable1; + delete[] pathTable2; + moveTable[lastUsedEntry] = 8; + return lastUsedEntry; +} + +int KyraEngine_v1::findSubPath(int x, int y, int toX, int toY, int *moveTable, int start, int end) { + // only used for debug specific code + //static uint16 unkTable[] = { 8, 5 }; + static const int8 facingTable1[] = { 7, 0, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 0 }; + static const int8 facingTable2[] = { -1, 0, -1, 2, -1, 4, -1, 6, -1, 2, -1, 4, -1, 6, -1, 0 }; + static const int8 facingTable3[] = { 2, 4, 4, 6, 6, 0, 0, 2, 6, 6, 0, 0, 2, 2, 4, 4 }; + static const int8 addPosTableX[] = { -1, 0, -1, 4, -1, 0, -1, -4, -1, -4, -1, 0, -1, 4, -1, 0 }; + static const int8 addPosTableY[] = { -1, 2, -1, 0, -1, -2, -1, 0, -1, 0, -1, 2, -1, 0, -1, -2 }; + + // debug specific + /*++unkTable[start]; + while (screen()->getPalette(0)[unkTable[start]] != 0x0F) { + ++unkTable[start]; + }*/ + + int xpos1 = x, xpos2 = x; + int ypos1 = y, ypos2 = y; + int newFacing = getFacingFromPointToPoint(x, y, toX, toY); + int position = 0; + + while (position != end) { + int newFacing2 = newFacing; + while (true) { + changePosTowardsFacing(xpos1, ypos1, facingTable1[start * 8 + newFacing2]); + if (!lineIsPassable(xpos1, ypos1)) { + if (facingTable1[start * 8 + newFacing2] == newFacing) + return 0x7D00; + newFacing2 = facingTable1[start * 8 + newFacing2]; + xpos1 = x; + ypos1 = y; + continue; + } + newFacing = facingTable1[start * 8 + newFacing2]; + break; + } + // debug drawing + /*if (xpos1 >= 0 && ypos1 >= 0 && xpos1 < 320 && ypos1 < 200) { + screen()->setPagePixel(0, xpos1, ypos1, unkTable[start]); + screen()->updateScreen(); + delayWithTicks(5); + }*/ + if (newFacing & 1) { + int temp = xpos1 + addPosTableX[newFacing + start * 8]; + if (toX == temp) { + temp = ypos1 + addPosTableY[newFacing + start * 8]; + if (toY == temp) { + moveTable[position++] = facingTable2[newFacing + start * 8]; + return position; + } + } + } + + moveTable[position++] = newFacing; + x = xpos1; + y = ypos1; + + if (x == toX && y == toY) + return position; + + if (xpos1 == xpos2 && ypos1 == ypos2) + break; + + newFacing = facingTable3[start * 8 + newFacing]; + } + + return 0x7D00; +} + +int KyraEngine_v1::getFacingFromPointToPoint(int x, int y, int toX, int toY) { + static const int facingTable[] = { + 1, 0, 1, 2, 3, 4, 3, 2, 7, 0, 7, 6, 5, 4, 5, 6 + }; + + int facingEntry = 0; + int ydiff = y - toY; + if (ydiff < 0) { + ++facingEntry; + ydiff = -ydiff; + } + facingEntry <<= 1; + + int xdiff = toX - x; + if (xdiff < 0) { + ++facingEntry; + xdiff = -xdiff; + } + + if (xdiff >= ydiff) { + int temp = ydiff; + ydiff = xdiff; + xdiff = temp; + + facingEntry <<= 1; + } else { + facingEntry <<= 1; + facingEntry += 1; + } + int temp = (ydiff + 1) >> 1; + + if (xdiff < temp) { + facingEntry <<= 1; + facingEntry += 1; + } else { + facingEntry <<= 1; + } + + assert(facingEntry < ARRAYSIZE(facingTable)); + return facingTable[facingEntry]; +} + + +int KyraEngine_v1::getOppositeFacingDirection(int dir) { + switch (dir) { + case 0: + return 2; + case 1: + return 1; + case 3: + return 7; + case 4: + return 6; + case 5: + return 5; + case 6: + return 4; + case 7: + return 3; + default: + break; + } + return 0; +} + +void KyraEngine_v1::changePosTowardsFacing(int &x, int &y, int facing) { + x += _addXPosTable[facing]; + y += _addYPosTable[facing]; +} + +int KyraEngine_v1::getMoveTableSize(int *moveTable) { + int tableSize = 0; + if (moveTable[0] == 8) + return 0; + + static const int facingTable[] = { + 4, 5, 6, 7, 0, 1, 2, 3 + }; + static const int unkTable[] = { + -1, -1, 1, 2, -1, 6, 7, -1, + -1, -1, -1, -1, 2, -1, 0, -1, + 1, -1, -1, -1, 3, 4, -1, 0, + 2, -1, -1, -1, -1, -1, 4, -1, + -1, 2, 3, -1, -1, -1, 5, 6, + 6, -1, 4, -1, -1, -1, -1, -1, + 7, 0, -1, 4, 5, -1, -1, -1, + -1, -1, 0, -1, 6, -1, -1, -1 + }; + + int *oldPosition = moveTable; + int *tempPosition = moveTable; + int *curPosition = moveTable + 1; + tableSize = 1; + + while (*curPosition != 8) { + if (*oldPosition == facingTable[*curPosition]) { + tableSize -= 2; + *oldPosition = 9; + *curPosition = 9; + + while (tempPosition != moveTable) { + --tempPosition; + if (*tempPosition != 9) + break; + } + + if (tempPosition == moveTable && *tempPosition == 9) { + while (*tempPosition == 9) + ++tempPosition; + + if (*tempPosition == 8) + return 0; + } + + oldPosition = tempPosition; + curPosition = oldPosition + 1; + + while (*curPosition == 9) + ++curPosition; + } else if (unkTable[*curPosition + *oldPosition * 8] != -1) { + --tableSize; + *oldPosition = unkTable[*curPosition + *oldPosition * 8]; + *curPosition = 9; + + if (tempPosition != oldPosition) { + curPosition = oldPosition; + oldPosition = tempPosition; + while (tempPosition != moveTable) { + --tempPosition; + if (*tempPosition != 9) + break; + } + } else { + do { + ++curPosition; + } while (*curPosition == 9); + } + } else { + tempPosition = oldPosition; + oldPosition = curPosition; + ++tableSize; + + do { + ++curPosition; + } while (*curPosition == 9); + } + } + + return tableSize; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/scene_v2.cpp b/engines/kyra/engine/scene_v2.cpp new file mode 100644 index 0000000000..dad8188542 --- /dev/null +++ b/engines/kyra/engine/scene_v2.cpp @@ -0,0 +1,227 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_v2.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_v2::freeSceneAnims() { + for (int i = 0; i < ARRAYSIZE(_sceneAnims); ++i) + _sceneAnims[i].flags = 0; + + for (int i = 0; i < ARRAYSIZE(_sceneAnimMovie); ++i) { + if (_sceneAnimMovie[i]) + _sceneAnimMovie[i]->close(); + } +} + +void KyraEngine_v2::updateSpecialSceneScripts() { + uint32 nextTime = _system->getMillis() + _tickLength; + const int startScript = _lastProcessedSceneScript; + + while (_system->getMillis() <= nextTime) { + if (_sceneSpecialScriptsTimer[_lastProcessedSceneScript] <= _system->getMillis() && + !_specialSceneScriptState[_lastProcessedSceneScript]) { + _specialSceneScriptRunFlag = true; + + while (_specialSceneScriptRunFlag && _sceneSpecialScriptsTimer[_lastProcessedSceneScript] <= _system->getMillis()) { + if (!_emc->run(&_sceneSpecialScripts[_lastProcessedSceneScript])) + _specialSceneScriptRunFlag = false; + } + } + + if (!_emc->isValid(&_sceneSpecialScripts[_lastProcessedSceneScript])) { + _emc->start(&_sceneSpecialScripts[_lastProcessedSceneScript], _desc.firstAnimSceneScript + _lastProcessedSceneScript); + _specialSceneScriptRunFlag = false; + } + + ++_lastProcessedSceneScript; + if (_lastProcessedSceneScript >= 10) + _lastProcessedSceneScript = 0; + + if (_lastProcessedSceneScript == startScript) + return; + } +} + +void KyraEngine_v2::runSceneScript6() { + _emc->init(&_sceneScriptState, &_sceneScriptData); + + _sceneScriptState.regs[0] = _mainCharacter.sceneId; + _sceneScriptState.regs[1] = _mouseX; + _sceneScriptState.regs[2] = _mouseY; + _sceneScriptState.regs[4] = _itemInHand; + + _emc->start(&_sceneScriptState, 6); + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +#pragma mark - pathfinder + +int KyraEngine_v2::findWay(int x, int y, int toX, int toY, int *moveTable, int moveTableSize) { + x &= ~3; toX &= ~3; + y &= ~1; toY &= ~1; + int size = KyraEngine_v1::findWay(x, y, toX, toY, moveTable, moveTableSize); + + if (size && !_smoothingPath) { + _smoothingPath = true; + int temp = pathfinderInitPositionTable(moveTable); + temp = pathfinderInitPositionIndexTable(temp, x, y); + pathfinderFinializePath(moveTable, temp, x, y, moveTableSize); + _smoothingPath = false; + } + + return _smoothingPath ? size : getMoveTableSize(moveTable); +} + +bool KyraEngine_v2::directLinePassable(int x, int y, int toX, int toY) { + Screen *scr = screen(); + + while (x != toX || y != toY) { + int facing = getFacingFromPointToPoint(x, y, toX, toY); + x += _addXPosTable[facing]; + y += _addYPosTable[facing]; + if (!scr->getShapeFlag1(x, y)) + return false; + } + + return true; +} + +int KyraEngine_v2::pathfinderInitPositionTable(int *moveTable) { + bool breakLoop = false; + int *moveTableCur = moveTable; + int oldEntry = *moveTableCur, curEntry = *moveTableCur; + int oldX = 0, newX = 0, oldY = 0, newY = 0; + int lastEntry = 0; + lastEntry = pathfinderAddToPositionTable(lastEntry, 0, 0); + + while (*moveTableCur != 8) { + oldEntry = curEntry; + + while (true) { + curEntry = *moveTableCur; + if (curEntry >= 0 && curEntry <= 7) + break; + + if (curEntry == 8) { + breakLoop = true; + break; + } else { + ++moveTableCur; + } + } + + if (breakLoop) + break; + + oldX = newX; + oldY = newY; + + newX += _addXPosTable[curEntry]; + newY += _addYPosTable[curEntry]; + + int temp = ABS(curEntry - oldEntry); + if (temp > 4) { + temp = 8 - temp; + } + + if (temp > 1 || oldEntry != curEntry) + lastEntry = pathfinderAddToPositionTable(lastEntry, oldX, oldY); + + ++moveTableCur; + } + + lastEntry = pathfinderAddToPositionTable(lastEntry, newX, newY); + _pathfinderPositionTable[lastEntry * 2 + 0] = -1; + _pathfinderPositionTable[lastEntry * 2 + 1] = -1; + return lastEntry; +} + +int KyraEngine_v2::pathfinderAddToPositionTable(int index, int v1, int v2) { + _pathfinderPositionTable[index << 1] = v1; + _pathfinderPositionTable[(index << 1) + 1] = v2; + ++index; + if (index >= 199) + --index; + return index; +} + +int KyraEngine_v2::pathfinderInitPositionIndexTable(int tableLen, int x, int y) { + int x1 = 0, y1 = 0; + int x2 = 0, y2 = 0; + int lastEntry = 0; + int index2 = tableLen - 1, index1 = 0; + while (index2 > index1) { + x1 = _pathfinderPositionTable[index1 * 2 + 0] + x; + y1 = _pathfinderPositionTable[index1 * 2 + 1] + y; + x2 = _pathfinderPositionTable[index2 * 2 + 0] + x; + y2 = _pathfinderPositionTable[index2 * 2 + 1] + y; + + if (directLinePassable(x1, y1, x2, y2)) { + lastEntry = pathfinderAddToPositionIndexTable(lastEntry, index2); + if (tableLen - 1 == index2) + break; + index1 = index2; + index2 = tableLen - 1; + } else if (index1 + 1 == index2) { + lastEntry = pathfinderAddToPositionIndexTable(lastEntry, index2); + index1 = index2; + index2 = tableLen - 1; + } else { + --index2; + } + } + return lastEntry; +} + +int KyraEngine_v2::pathfinderAddToPositionIndexTable(int index, int v) { + _pathfinderPositionIndexTable[index] = v; + ++index; + if (index >= 199) + --index; + return index; +} + +void KyraEngine_v2::pathfinderFinializePath(int *moveTable, int tableLen, int x, int y, int moveTableSize) { + int x1 = 0, y1 = 0; + int x2 = 0, y2 = 0; + int index1 = 0, index2 = 0; + int sizeLeft = moveTableSize; + for (int i = 0; i < tableLen; ++i) { + index2 = _pathfinderPositionIndexTable[i]; + x1 = _pathfinderPositionTable[index1 * 2 + 0] + x; + y1 = _pathfinderPositionTable[index1 * 2 + 1] + y; + x2 = _pathfinderPositionTable[index2 * 2 + 0] + x; + y2 = _pathfinderPositionTable[index2 * 2 + 1] + y; + + int wayLen = findWay(x1, y1, x2, y2, moveTable, sizeLeft); + moveTable += wayLen; + sizeLeft -= wayLen; // unlike the original we want to be sure that the size left is correct + index1 = index2; + } +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/sprites.cpp b/engines/kyra/engine/sprites.cpp new file mode 100644 index 0000000000..197d8eab4e --- /dev/null +++ b/engines/kyra/engine/sprites.cpp @@ -0,0 +1,575 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/sprites.h" +#include "kyra/resource/resource.h" +#include "kyra/graphics/animator_lok.h" + +#include "common/system.h" + +namespace Kyra { + +Sprites::Sprites(KyraEngine_LoK *vm, OSystem *system) : _rnd("kyraSprites") { + _vm = vm; + _res = vm->resource(); + _screen = vm->screen(); + _system = system; + _dat = 0; + memset(_anims, 0, sizeof(_anims)); + memset(_sceneShapes, 0, sizeof(_sceneShapes)); + _spriteDefStart = 0; + memset(_drawLayerTable, 0, sizeof(_drawLayerTable)); + _sceneAnimatorBeaconFlag = 0; +} + +Sprites::~Sprites() { + delete[] _dat; + freeSceneShapes(); + for (int i = 0; i < MAX_NUM_ANIMS; i++) { + if (_anims[i].background) + delete[] _anims[i].background; + } +} + +void Sprites::setupSceneAnims() { + uint8 *data; + + for (int i = 0; i < MAX_NUM_ANIMS; i++) { + delete[] _anims[i].background; + _anims[i].background = 0; + + if (_anims[i].script != 0) { + data = _anims[i].script; + + assert(READ_LE_UINT16(data) == 0xFF86); + data += 4; + + _anims[i].disable = READ_LE_UINT16(data) != 0; + data += 4; + _anims[i].unk2 = READ_LE_UINT16(data); + data += 4; + + if ((_vm->_northExitHeight & 0xFF) > READ_LE_UINT16(data)) + _anims[i].drawY = _vm->_northExitHeight & 0xFF; + else + _anims[i].drawY = READ_LE_UINT16(data); + data += 4; + + //sceneUnk2[i] = READ_LE_UINT16(data); + data += 4; + + _anims[i].x = READ_LE_UINT16(data); + data += 4; + _anims[i].y = READ_LE_UINT16(data); + data += 4; + _anims[i].width = *(data); + data += 4; + _anims[i].height = *(data); + data += 4; + _anims[i].sprite = READ_LE_UINT16(data); + data += 4; + _anims[i].flipX = READ_LE_UINT16(data) != 0; + data += 4; + _anims[i].width2 = *(data); + data += 4; + _anims[i].height2 = *(data); + data += 4; + _anims[i].unk1 = READ_LE_UINT16(data) != 0; + data += 4; + _anims[i].play = READ_LE_UINT16(data) != 0; + data += 2; + + _anims[i].script = data; + _anims[i].curPos = data; + + int bkgdWidth = _anims[i].width; + int bkgdHeight = _anims[i].height; + + if (_anims[i].width2 > 0) + bkgdWidth += (_anims[i].width2 >> 3) + 1; + + if (_anims[i].height2 > 0) + bkgdHeight += _anims[i].height2; + + _anims[i].background = new uint8[_screen->getRectSize(bkgdWidth + 1, bkgdHeight)]; + assert(_anims[i].background); + memset(_anims[i].background, 0, _screen->getRectSize(bkgdWidth + 1, bkgdHeight)); + } + } +} + +void Sprites::updateSceneAnims() { + uint32 currTime = _system->getMillis(); + bool update; + uint8 *data; + uint16 rndNr; + uint16 anim; + uint16 sound; + + for (int i = 0; i < MAX_NUM_ANIMS; i++) { + if (_anims[i].script == 0 || !_anims[i].play || (_anims[i].nextRun != 0 && _anims[i].nextRun > currTime)) + continue; + + data = _anims[i].curPos; + update = true; + debugC(6, kDebugLevelSprites, "anim: %d 0x%.04X", i, READ_LE_UINT16(data)); + assert((data - _anims[i].script) < _anims[i].length); + switch (READ_LE_UINT16(data)) { + case 0xFF88: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set sprite image."); + debugC(6, kDebugLevelSprites, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + data += 2; + //debugC(6, kDebugLevelSprites, "Unused %i", READ_LE_UINT16(data)); + data += 2; + debugC(6, kDebugLevelSprites, "X %i", READ_LE_UINT16(data)); + _anims[i].x = READ_LE_UINT16(data); + data += 2; + debugC(6, kDebugLevelSprites, "Y %i", READ_LE_UINT16(data)); + _anims[i].y = READ_LE_UINT16(data); + data += 2; + _anims[i].flipX = false; + _anims[i].lastRefresh = _system->getMillis(); + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF8D: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set sprite image, flipped."); + debugC(6, kDebugLevelSprites, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + data += 2; + data += 2; + debugC(6, kDebugLevelSprites, "X %i", READ_LE_UINT16(data)); + _anims[i].x = READ_LE_UINT16(data); + data += 2; + debugC(6, kDebugLevelSprites, "Y %i", READ_LE_UINT16(data)); + _anims[i].y = READ_LE_UINT16(data); + data += 2; + _anims[i].flipX = true; + _anims[i].lastRefresh = _system->getMillis(); + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF8A: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set time to wait"); + debugC(6, kDebugLevelSprites, "Time %i", READ_LE_UINT16(data)); + _anims[i].nextRun = _system->getMillis() + READ_LE_UINT16(data) * _vm->tickLength(); + _anims[i].nextRun -= _system->getMillis() - _anims[i].lastRefresh; + data += 2; + break; + case 0xFFB3: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set time to wait to random value"); + rndNr = READ_LE_UINT16(data) + _rnd.getRandomNumber( READ_LE_UINT16(data) + 2); + debugC(6, kDebugLevelSprites, "Minimum time %i", READ_LE_UINT16(data)); + data += 2; + debugC(6, kDebugLevelSprites, "Maximum time %i", READ_LE_UINT16(data)); + data += 2; + _anims[i].nextRun = _system->getMillis() + rndNr * _vm->tickLength(); + _anims[i].nextRun -= _system->getMillis() - _anims[i].lastRefresh; + break; + case 0xFF8C: + data += 2; + debugC(6, kDebugLevelSprites, "func: Wait until wait time has elapsed"); + update = (_anims[i].nextRun < currTime); + //assert( _anims[i].nextRun > _system->getMillis()); + break; + case 0xFF99: + data += 2; + debugC(1, kDebugLevelSprites, "func: Set value of unknown animation property to 1"); + _anims[i].unk1 = 1; + break; + case 0xFF9A: + data += 2; + debugC(1, kDebugLevelSprites, "func: Set value of unknown animation property to 0"); + _anims[i].unk1 = 0; + break; + case 0xFF97: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set default X coordinate of sprite"); + debugC(6, kDebugLevelSprites, "X %i", READ_LE_UINT16(data)); + _anims[i].x = READ_LE_UINT16(data); + data += 2; + break; + case 0xFF98: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set default Y coordinate of sprite"); + debugC(6, kDebugLevelSprites, "Y %i", READ_LE_UINT16(data)); + _anims[i].y = READ_LE_UINT16(data); + data += 2; + break; + case 0xFF8B: + debugC(6, kDebugLevelSprites, "func: Jump to start of script section"); + _anims[i].curPos = _anims[i].script; + _anims[i].nextRun = _system->getMillis(); + update = false; + break; + case 0xFF8E: + data += 2; + debugC(6, kDebugLevelSprites, "func: Begin for () loop"); + debugC(6, kDebugLevelSprites, "Iterations: %i", READ_LE_UINT16(data)); + _anims[i].loopsLeft = READ_LE_UINT16(data); + data += 2; + _anims[i].loopStart = data; + break; + case 0xFF8F: + data += 2; + debugC(6, kDebugLevelSprites, "func: End for () loop"); + if (_anims[i].loopsLeft > 0) { + _anims[i].loopsLeft--; + data = _anims[i].loopStart; + } + break; + case 0xFF90: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set sprite image using default X and Y"); + debugC(6, kDebugLevelSprites, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + _anims[i].flipX = false; + data += 2; + _anims[i].lastRefresh = _system->getMillis(); + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF91: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set sprite image using default X and Y, flipped."); + debugC(6, kDebugLevelSprites, "Sprite index %i", READ_LE_UINT16(data)); + _anims[i].sprite = READ_LE_UINT16(data); + _anims[i].flipX = true; + data += 2; + _anims[i].lastRefresh = _system->getMillis(); + refreshSceneAnimObject(i, _anims[i].sprite, _anims[i].x, _anims[i].y, _anims[i].flipX, _anims[i].unk1 != 0); + break; + case 0xFF92: + data += 2; + debugC(6, kDebugLevelSprites, "func: Increase value of default X-coordinate"); + debugC(6, kDebugLevelSprites, "Increment %i", READ_LE_UINT16(data)); + _anims[i].x += READ_LE_UINT16(data); + data += 2; + break; + case 0xFF93: + data += 2; + debugC(6, kDebugLevelSprites, "func: Increase value of default Y-coordinate"); + debugC(6, kDebugLevelSprites, "Increment %i", READ_LE_UINT16(data)); + _anims[i].y += READ_LE_UINT16(data); + data += 2; + break; + case 0xFF94: + data += 2; + debugC(6, kDebugLevelSprites, "func: Decrease value of default X-coordinate"); + debugC(6, kDebugLevelSprites, "Decrement %i", READ_LE_UINT16(data)); + _anims[i].x -= READ_LE_UINT16(data); + data += 2; + break; + case 0xFF95: + data += 2; + debugC(6, kDebugLevelSprites, "func: Decrease value of default Y-coordinate"); + debugC(6, kDebugLevelSprites, "Decrement %i", READ_LE_UINT16(data)); + _anims[i].y -= READ_LE_UINT16(data); + data += 2; + break; + case 0xFF96: + data += 2; + debugC(6, kDebugLevelSprites, "func: Stop animation"); + debugC(6, kDebugLevelSprites, "Animation index %i", READ_LE_UINT16(data)); + anim = READ_LE_UINT16(data); + data += 2; + _anims[anim].play = false; + _anims[anim].sprite = -1; + break; +/* case 0xFF97: + data += 2; + debugC(1, kDebugLevelSprites, "func: Set value of animation property 34h to 0"); + break;*/ + case 0xFFAD: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set Brandon's X coordinate"); + debugC(6, kDebugLevelSprites, "X %i", READ_LE_UINT16(data)); + _vm->currentCharacter()->x1 = READ_LE_UINT16(data); + data += 2; + break; + case 0xFFAE: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set Brandon's Y coordinate"); + debugC(6, kDebugLevelSprites, "Y %i", READ_LE_UINT16(data)); + _vm->currentCharacter()->y1 = READ_LE_UINT16(data); + data += 2; + break; + case 0xFFAF: + data += 2; + debugC(6, kDebugLevelSprites, "func: Set Brandon's sprite"); + debugC(6, kDebugLevelSprites, "Sprite %i", READ_LE_UINT16(data)); + _vm->currentCharacter()->currentAnimFrame = READ_LE_UINT16(data); + data += 2; + break; + case 0xFFAA: + data += 2; + debugC(6, kDebugLevelSprites, "func: Reset Brandon's sprite"); + _vm->animator()->actors()->sceneAnimPtr = 0; + _vm->animator()->actors()->bkgdChangeFlag = 1; + _vm->animator()->actors()->refreshFlag = 1; + _vm->animator()->restoreAllObjectBackgrounds(); + _vm->animator()->flagAllObjectsForRefresh(); + _vm->animator()->updateAllObjectShapes(); + break; + case 0xFFAB: + data += 2; + debugC(6, kDebugLevelSprites, "func: Update Brandon's sprite"); + _vm->animator()->animRefreshNPC(0); + _vm->animator()->flagAllObjectsForRefresh(); + _vm->animator()->updateAllObjectShapes(); + break; + case 0xFFB0: + data += 2; + debugC(6, kDebugLevelSprites, "func: Play sound"); + debugC(6, kDebugLevelSprites, "Sound index %i", READ_LE_UINT16(data)); + _vm->snd_playSoundEffect(READ_LE_UINT16(data)); + data += 2; + break; + case 0xFFB1: + data += 2; + _sceneAnimatorBeaconFlag = 1; + break; + case 0xFFB2: + data += 2; + _sceneAnimatorBeaconFlag = 0; + break; + case 0xFFB4: + data += 2; + debugC(6, kDebugLevelSprites, "func: Play (at random) a certain sound at a certain percentage of time"); + debugC(6, kDebugLevelSprites, "Sound index %i", READ_LE_UINT16(data)); + sound = READ_LE_UINT16(data); + data += 2; + debugC(6, kDebugLevelSprites, "Percentage %i", READ_LE_UINT16(data)); + rndNr = _rnd.getRandomNumber(100); + if (rndNr <= READ_LE_UINT16(data)) + _vm->snd_playSoundEffect(sound); + data += 2; + break; + case 0xFFA7: + data += 2; + debugC(6, kDebugLevelSprites, "func: Play animation"); + debugC(6, kDebugLevelSprites, "Animation index %i", READ_LE_UINT16(data)); + _anims[READ_LE_UINT16(data)].play = 1; + data += 2; + break; + default: + warning("Unsupported anim command %X in script %i", READ_LE_UINT16(data), i); + data += 2; + } + + if (update) + _anims[i].curPos = data; + if (READ_LE_UINT16(data) == 0xFF87) + _anims[i].play = false; + } +} + +void Sprites::loadDat(const char *filename, SceneExits &exits) { + uint32 fileSize; + + delete[] _dat; + _spriteDefStart = 0; + + _res->exists(filename, true); + _dat = _res->fileData(filename, &fileSize); + + for (uint i = 0; i < MAX_NUM_ANIMS; ++i) + delete[] _anims[i].background; + + memset(_anims, 0, sizeof(_anims)); + uint8 nextAnim = 0; + + assert(fileSize > 0x6D); + + memcpy(_drawLayerTable, (_dat + 0x0D), 8); + _vm->_northExitHeight = READ_LE_UINT16(_dat + 0x15); + if (_vm->_northExitHeight & 1) + _vm->_northExitHeight += 1; + + // XXX + _vm->_paletteChanged = 1; + + if (_vm->gameFlags().platform == Common::kPlatformAmiga) { + if (_vm->queryGameFlag(0xA0)) + _screen->copyPalette(3, 4); + else + _screen->copyPalette(3, 0); + } else { + if (_vm->queryGameFlag(0xA0)) + _screen->copyPalette(1, 3); + else + _screen->copyPalette(1, 0); + + _screen->getPalette(1).copy(_dat + 0x17, 0, 20, 228); + } + uint8 *data = _dat + 0x6B; + + uint16 length = READ_LE_UINT16(data); + data += 2; + + if (length > 2) { + assert( length < fileSize); + uint8 *animstart; + uint8 *start = data; + + while (1) { + if (((uint16)(data - _dat) >= fileSize) || (data - start) >= length) + break; + + if (READ_LE_UINT16(data) == 0xFF83) { + //debugC(1, kDebugLevelSprites, "Body section end."); + data += 2; + break; + } + + switch (READ_LE_UINT16(data)) { + case 0xFF81: + data += 2; + //debugC(1, kDebugLevelSprites, "Body section start"); + break; + case 0xFF82: + data += 2; + //debugC(1, kDebugLevelSprites, "Unknown 0xFF82 section"); + break; + case 0xFF84: + data += 2; + _spriteDefStart = data; + while (READ_LE_UINT16(data) != 0xFF85) + data += 2; + data += 2; + break; + case 0xFF86: + assert(nextAnim < MAX_NUM_ANIMS); + _anims[nextAnim].script = data; + _anims[nextAnim].curPos = data; + _anims[nextAnim].sprite = -1; + _anims[nextAnim].play = true; + animstart = data; + data += 2; + while (READ_LE_UINT16(data) != 0xFF87) { + assert((uint16)(data - _dat) < fileSize); + data += 2; + } + _anims[nextAnim].length = data - animstart; + //debugC(1, kDebugLevelSprites, "Found an anim script of length %i", _anims[nextAnim].length); + nextAnim++; + data += 2; + break; + default: + warning("Unknown code in DAT file '%s' offset %d: %x", filename, int(data - _dat), READ_LE_UINT16(data)); + data += 2; + } + } + } else { + data += 2; + } + + assert(fileSize - (data - _dat) == 0xC); + + exits.northXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.northYPos = *data++ & 0xFFFE; + exits.eastXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.eastYPos = *data++ & 0xFFFE; + exits.southXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.southYPos = *data++ & 0xFFFE; + exits.westXPos = READ_LE_UINT16(data) & 0xFFFC; data += 2; + exits.westYPos = *data++ & 0xFFFE; +} + +void Sprites::freeSceneShapes() { + for (int i = 0; i < ARRAYSIZE(_sceneShapes); i++) { + delete[] _sceneShapes[i]; + _sceneShapes[i] = 0; + } +} + +void Sprites::loadSceneShapes() { + uint8 *data = _spriteDefStart; + int spriteNum, x, y, width, height; + + freeSceneShapes(); + memset( _sceneShapes, 0, sizeof(_sceneShapes)); + + if (_spriteDefStart == 0) + return; + + int bakPage = _screen->_curPage; + _screen->_curPage = 3; + + while (READ_LE_UINT16(data) != 0xFF85) { + spriteNum = READ_LE_UINT16(data); + assert(spriteNum < ARRAYSIZE(_sceneShapes)); + data += 2; + x = READ_LE_UINT16(data) * 8; + data += 2; + y = READ_LE_UINT16(data); + data += 2; + width = READ_LE_UINT16(data) * 8; + data += 2; + height = READ_LE_UINT16(data); + data += 2; + _sceneShapes[spriteNum] = _screen->encodeShape(x, y, width, height, 2); + } + _screen->_curPage = bakPage; +} + +void Sprites::refreshSceneAnimObject(uint8 animNum, uint8 shapeNum, uint16 x, uint16 y, bool flipX, bool unkFlag) { + Animator_LoK::AnimObject &anim = _vm->animator()->sprites()[animNum]; + anim.refreshFlag = 1; + anim.bkgdChangeFlag = 1; + + if (unkFlag) + anim.flags |= 0x0200; + else + anim.flags &= 0xFD00; + + if (flipX) + anim.flags |= 1; + else + anim.flags &= 0xFE; + + anim.sceneAnimPtr = _sceneShapes[shapeNum]; + anim.animFrameNumber = -1; + anim.x1 = x; + anim.y1 = y; +} + +int Sprites::getDrawLayer(int y) { + uint8 returnValue = 0; + for (int i = 0; i < ARRAYSIZE(_drawLayerTable); ++i) { + uint8 temp = _drawLayerTable[i]; + if (temp) { + if (temp <= y) + returnValue = i; + } + } + + if (returnValue <= 0) + returnValue = 1; + else if (returnValue >= 7) + returnValue = 6; + + return returnValue; +} +} // End of namespace Kyra diff --git a/engines/kyra/engine/sprites.h b/engines/kyra/engine/sprites.h new file mode 100644 index 0000000000..f68f36ffa4 --- /dev/null +++ b/engines/kyra/engine/sprites.h @@ -0,0 +1,99 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SPRITES_H +#define KYRA_SPRITES_H + +#include "kyra/engine/kyra_lok.h" +#include "common/random.h" + +namespace Kyra { + +#define MAX_NUM_ANIMS 11 + +struct Sprite { + uint16 x; + uint16 y; + uint16 width; + uint16 height; +}; + +struct Anim { + uint8 *script; + uint8 *curPos; + uint16 length; + int16 x; + int16 y; + bool flipX; + int8 sprite; + uint8 *loopStart; + uint16 loopsLeft; + uint32 nextRun; + uint32 lastRefresh; + bool play; + uint16 width; + uint16 height; + uint16 width2; + uint16 height2; + uint16 unk1; + uint16 drawY; + uint16 unk2; + uint8 *background; + bool disable; +}; + +class KyraEngine_LoK; + +class Sprites { +public: + Sprites(KyraEngine_LoK *vm, OSystem *system); + ~Sprites(); + + void updateSceneAnims(); + void setupSceneAnims(); + void loadDat(const char *filename, SceneExits &exits); + void loadSceneShapes(); + + Anim _anims[MAX_NUM_ANIMS]; + uint8 *_sceneShapes[50]; + + void refreshSceneAnimObject(uint8 animNum, uint8 shapeNum, uint16 x, uint16 y, bool flipX, bool unkFlag); + + int getDrawLayer(int y); + + int _sceneAnimatorBeaconFlag; +protected: + void freeSceneShapes(); + + KyraEngine_LoK *_vm; + Resource *_res; + OSystem *_system; + Screen *_screen; + uint8 *_dat; + Common::RandomSource _rnd; + uint8 *_spriteDefStart; + uint8 _drawLayerTable[8]; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/sprites_eob.cpp b/engines/kyra/engine/sprites_eob.cpp new file mode 100644 index 0000000000..d7bfe7413d --- /dev/null +++ b/engines/kyra/engine/sprites_eob.cpp @@ -0,0 +1,1285 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/script/script_eob.h" +#include "kyra/resource/resource.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + + +namespace Kyra { + +void EoBCoreEngine::loadMonsterShapes(const char *filename, int monsterIndex, bool hasDecorations, int encodeTableIndex) { + if (_flags.platform == Common::kPlatformFMTowns) { + Common::String tmp = Common::String::format("%s.MNT", filename); + Common::SeekableReadStream *s = _res->createReadStream(tmp); + if (!s) + error("Screen_EoB::loadMonsterShapes(): Failed to load file '%s'", tmp.c_str()); + + for (int i = 0; i < 6; i++) + _monsterShapes[monsterIndex + i] = loadTownsShape(s); + + for (int i = 0; i < 6; i++) { + for (int ii = 0; ii < 2; ii++) + s->read(_monsterPalettes[(monsterIndex >= 18 ? i + 6 : i) * 2 + ii], 16); + } + + if (hasDecorations) + loadMonsterDecoration(s, monsterIndex); + + delete s; + } else { + _screen->loadShapeSetBitmap(filename, 3, 3); + const uint16 *enc = &_encodeMonsterShpTable[encodeTableIndex << 2]; + + for (int i = 0; i < 6; i++, enc += 4) + _monsterShapes[monsterIndex + i] = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, _cgaMappingDefault); + + generateMonsterPalettes(filename, monsterIndex); + + if (hasDecorations) { + Common::SeekableReadStream *s = _res->createReadStream(Common::String::format("%s.DCR", filename)); + if (s) + loadMonsterDecoration(s, monsterIndex); + delete s; + } + } + _screen->_curPage = 0; +} + +void EoBCoreEngine::releaseMonsterShapes(int first, int num) { + for (int i = first; i < first + num; i++) { + delete[] _monsterShapes[i]; + _monsterShapes[i] = 0; + delete[] _monsterDecorations[i].shp; + _monsterDecorations[i].shp = 0; + } +} + +uint8 *EoBCoreEngine::loadTownsShape(Common::SeekableReadStream *stream) { + uint32 size = stream->readUint32LE(); + uint8 *shape= new uint8[size]; + stream->read(shape, size); + if (shape[0] == 1) + shape[0]++; + return shape; +} + +const uint8 *EoBCoreEngine::loadMonsterProperties(const uint8 *data) { + uint8 cmd = *data++; + while (cmd != 0xFF) { + EoBMonsterProperty *d = &_monsterProps[cmd]; + d->armorClass = (int8)*data++; + d->hitChance = (int8)*data++; + d->level = (int8)*data++; + d->hpDcTimes = *data++; + d->hpDcPips = *data++; + d->hpDcBase = *data++; + d->attacksPerRound = *data++; + d->dmgDc[0].times = *data++; + d->dmgDc[0].pips = *data++; + d->dmgDc[0].base = (int8)*data++; + d->dmgDc[1].times = *data++; + d->dmgDc[1].pips = *data++; + d->dmgDc[1].base = (int8)*data++; + d->dmgDc[2].times = *data++; + d->dmgDc[2].pips = *data++; + d->dmgDc[2].base = (int8)*data++; + d->immunityFlags = READ_LE_UINT16(data); + data += 2; + d->capsFlags = READ_LE_UINT16(data); + data += 2; + d->typeFlags = READ_LE_UINT16(data); + data += 2; + d->experience = READ_LE_UINT16(data); + data += 2; + + d->u30 = *data++; + d->sound1 = (int8)*data++; + d->sound2 = (int8)*data++; + d->numRemoteAttacks = *data++; + + if (*data++ != 0xFF) { + d->remoteWeaponChangeMode = *data++; + d->numRemoteWeapons = *data++; + + for (int i = 0; i < d->numRemoteWeapons; i++) { + d->remoteWeapons[i] = (int8)*data; + data += 2; + } + } + + d->tuResist = (int8)*data++; + d->dmgModifierEvade = *data++; + + for (int i = 0; i < 3; i++) + d->decorations[i] = *data++; + + cmd = *data++; + } + + return data; +} + +const uint8 *EoBCoreEngine::loadActiveMonsterData(const uint8 *data, int level) { + for (uint8 p = *data++; p != 0xFF; p = *data++) { + uint8 v = *data++; + _timer->setCountdown(0x20 + (p << 1), v); + _timer->setCountdown(0x21 + (p << 1), v); + } + + uint32 ct = _system->getMillis(); + for (int i = 0x20; i < 0x24; i++) { + int32 del = _timer->getDelay(i); + _timer->setNextRun(i, (i & 1) ? ct + (del >> 1) * _tickLength : ct + del * _tickLength); + } + _timer->resetNextRun(); + + if (_hasTempDataFlags & (1 << (level - 1))) + return data + 420; + + memset(_monsters, 0, 30 * sizeof(EoBMonsterInPlay)); + + for (int i = 0; i < 30; i++, data += 14) { + if (*data == 0xFF) + continue; + + initMonster(data[0], data[1], READ_LE_UINT16(&data[2]), data[4], (int8)data[5], data[6], data[7], data[8], data[9], READ_LE_UINT16(&data[10]), READ_LE_UINT16(&data[12])); + _monsters[data[0]].flags |= 0x40; + } + + return data; +} + +void EoBCoreEngine::initMonster(int index, int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int i, int randItem, int fixedItem) { + EoBMonsterInPlay *m = &_monsters[index]; + EoBMonsterProperty *p = &_monsterProps[type]; + memset(m, 0, sizeof(EoBMonsterInPlay)); + + if (!block) + return; + + unit <<= 1; + if (index & 1) + unit++; + + m->stepsTillRemoteAttack = _flags.gameID == GI_EOB2 ? rollDice(1, 3, 0) : 5; + m->type = type; + m->numRemoteAttacks = p->numRemoteAttacks; + m->curRemoteWeapon = 0; + m->unit = unit; + m->pos = pos; + m->shpIndex = shpIndex; + m->mode = mode; + m->spellStatusLeft = i; + m->dir = dir; + m->palette = _flags.gameID == GI_EOB2 ? (index % 3) : 0; + m->hitPointsCur = m->hitPointsMax = _flags.gameID == GI_EOB2 ? rollDice(p->hpDcTimes, p->hpDcPips, p->hpDcBase) : (p->level == -1 ? rollDice(1, 4, 0) : rollDice(p->level, 8, 0)); + m->randItem = randItem; + m->fixedItem = fixedItem; + m->sub = _currentSub; + + placeMonster(m, block, dir); +} + +void EoBCoreEngine::placeMonster(EoBMonsterInPlay *m, uint16 block, int dir) { + if (block != 0xFFFF) { + checkSceneUpdateNeed(m->block); + if (_levelBlockProperties[m->block].flags & 7) { + _levelBlockProperties[m->block].flags--; + if (_flags.gameID == GI_EOB2) + runLevelScript(m->block, 0x400); + } + m->block = block; + _levelBlockProperties[block].flags++; + if (_flags.gameID == GI_EOB2) + runLevelScript(m->block, 0x200); + } + + if (dir != -1) { + m->dir = dir; + block = m->block; + } + + checkSceneUpdateNeed(block); +} + +void EoBCoreEngine::killMonster(EoBMonsterInPlay *m, bool giveExperience) { + m->hitPointsCur = -1; + int pos = (m->pos == 4) ? rollDice(1, 4, -1) : m->pos; + + if (m->randItem) { + if (rollDice(1, 10, 0) == 1) + setItemPosition((Item *)&_levelBlockProperties[m->block & 0x3FF].drawObjects, m->block, duplicateItem(m->randItem), pos); + } + + if (m->fixedItem) + setItemPosition((Item *)&_levelBlockProperties[m->block & 0x3FF].drawObjects, m->block, duplicateItem(m->fixedItem), pos); + + if (giveExperience) + increasePartyExperience(_monsterProps[m->type].experience); + + if (killMonsterExtra(m)) { + placeMonster(m, 0, -1); + + if (m->mode == 8) + updateAttackingMonsterFlags(); + } +} + +int EoBCoreEngine::countSpecificMonsters(int type) { + int res = 0; + for (int i = 0; i < 30; i++) { + if (_monsters[i].type != type || _monsters[i].sub != _currentSub || _monsters[i].hitPointsCur < 0) + continue; + res++; + } + return res; +} + +void EoBCoreEngine::updateAttackingMonsterFlags() { + EoBMonsterInPlay *m2 = 0; + for (EoBMonsterInPlay *m = _monsters; m < &_monsters[30]; m++) { + if (m->mode != 8) + continue; + m->mode = 0; + m->dest = _currentBlock; + m2 = m; + } + + if (!m2) + return; + + if (m2->type == 7) + setScriptFlags(4); + + if (m2->type == 12) + setScriptFlags(0x800); +} + +const int8 *EoBCoreEngine::getMonstersOnBlockPositions(uint16 block) { + memset(_monsterBlockPosArray, -1, sizeof(_monsterBlockPosArray)); + for (int8 i = 0; i < 30; i++) { + if (_monsters[i].block != block) + continue; + assert(_monsters[i].pos < sizeof(_monsterBlockPosArray)); + _monsterBlockPosArray[_monsters[i].pos] = i; + } + return _monsterBlockPosArray; +} + +int EoBCoreEngine::getClosestMonster(int charIndex, int block) { + const int8 *pos = getMonstersOnBlockPositions(block); + if (pos[4] != -1) + return pos[4]; + + const uint8 *p = &_monsterProximityTable[(_currentDirection << 3) + ((charIndex & 1) << 2)]; + for (int i = 0; i < 4; i++) { + if (pos[p[i]] != -1) + return pos[p[i]]; + } + return -1; +} + +bool EoBCoreEngine::blockHasMonsters(uint16 block) { + return _levelBlockProperties[block].flags & 7 ? true : false; +} + +bool EoBCoreEngine::isMonsterOnPos(EoBMonsterInPlay *m, uint16 block, int pos, int checkPos4) { + return (m->block == block && (m->pos == pos || (m->pos == 4 && checkPos4))) ? true : false; +} + +const int16 *EoBCoreEngine::findBlockMonsters(uint16 block, int pos, int dir, int blockDamage, int singleTargetCheckAdjacent) { + static const uint8 cpos4[] = { 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1 }; + int include4 = (pos < 4) ? cpos4[(dir << 2) + pos] : 1; + int16 *dst = _foundMonstersArray; + + if (blockDamage) { + for (int i = 0; i < 30; i++) { + if (_monsters[i].block == block && (_monsters[i].pos != 4 || include4)) + *dst++ = i; + } + + } else if (singleTargetCheckAdjacent) { + int16 r = -1; + int f = 5; + + for (int i = 0; i < 30; i++) { + const uint8 *tbl = &_findBlockMonstersTable[(dir << 4) + (pos << 2)]; + + if (_monsters[i].block != block) + continue; + + if (_monsters[i].pos == pos) { + r = i; + break; + } + + for (int ii = 0; ii < 4; ii++) { + if (_monsters[i].pos == tbl[ii] && ii < f) { + f = ii; + r = i; + } + } + } + + *dst++ = r; + + } else { + for (int i = 0; i < 30; i++) { + if (isMonsterOnPos(&_monsters[i], block, pos, include4)) + *dst++ = i; + } + } + + *dst = -1; + return _foundMonstersArray; +} + +void EoBCoreEngine::drawBlockObject(int flipped, int page, const uint8 *shape, int x, int y, int sd, uint8 *ovl) { + const ScreenDim *d = _screen->getScreenDim(sd); + if (_flags.gameID == GI_EOB1) + x &= ~1; + _screen->drawShape(page, shape, x - (d->sx << 3), y - d->sy, sd, flipped | (ovl ? 2 : 0), ovl); +} + +void EoBCoreEngine::drawMonsterShape(const uint8 *shape, int x, int y, int flipped, int flags, int palIndex) { + uint8 *ovl = 0; + + if (flags & 2) + ovl = _monsterFlashOverlay; + else if (_flags.gameID == GI_EOB2 && flags & 0x20) + ovl = _monsterStoneOverlay; + else if (palIndex != -1) + ovl = _monsterPalettes[palIndex]; + + drawBlockObject(flipped, 2, shape, x, y, 5, ovl); +} + +void EoBCoreEngine::flashMonsterShape(EoBMonsterInPlay *m) { + disableSysTimer(2); + _flashShapeTimer = 0; + drawScene(1); + m->flags &= 0xFD; + _flashShapeTimer = _system->getMillis() + _tickLength; + enableSysTimer(2); + + _sceneUpdateRequired = true; +} + +void EoBCoreEngine::updateAllMonsterShapes() { + drawScene(1); + bool updateShp = false; + + for (EoBMonsterInPlay *m = _monsters; m < &_monsters[30]; m++) { + if (m->flags & 2) { + m->flags &= ~2; + updateShp = true; + if (m->hitPointsCur <= 0) + killMonster(m, true); + } + } + + if (updateShp) { + _sceneUpdateRequired = true; + _flashShapeTimer = _system->getMillis() + _tickLength; + } else { + _sceneUpdateRequired = false; + } + _preventMonsterFlash = false; +} + +void EoBCoreEngine::drawBlockItems(int index) { + uint16 o = _visibleBlocks[index]->drawObjects; + uint8 w = _visibleBlocks[index]->walls[_sceneDrawVarDown]; + uint8 flg = (index == 16) ? 0x80 : _wllWallFlags[w]; + + if (_wllVmpMap[w] && !(flg & 0x80)) + return; + + uint16 o2 = o = _items[o].next; + bool forceLoop = true; + static const int8 itemPosYNiche[] = { 0x25, 0x31, 0x38, 0x00 }; + static const int8 itemPosFin[] = { 0, -2, 1, -1, 2, 0, 1, -1 }; + int tile2 = 0; + + while (o != o2 || forceLoop) { + EoBItem *itm = &_items[o]; + if (itm->pos == 8 || itm->pos < 4) { + tile2 = -1; + + uint8 ps = (itm->pos == 8) ? 4 : _dscItemPosIndex[(_currentDirection << 2) + (itm->pos & 3)]; + uint16 wo = (index * 5 + ps) << 1; + int x = _dscShapeCoords[wo] + 88; + int y = 0; + + if (itm->pos == 8) { + x = _dscItemShpX[index]; + y = itemPosYNiche[_dscDimMap[index]]; + ps = 0; + } else { + y = _dscShapeCoords[wo + 1] + 124; + } + + int8 scaleSteps = (int8)_dscItemScaleIndex[(_dscDimMap[index] << 2) + ps]; + if ((flg & 8) && ps < 2 && scaleSteps) { + tile2 = _dscItemTileIndex[index]; + if (tile2 != -1) + setLevelShapesDim(tile2, _shpDmX1, _shpDmX2, 5); + y -= 4; + } + + if (scaleSteps >= 0) { + const uint8 *shp = _screen->scaleShape(_dscItemShapeMap[itm->icon] < _numLargeItemShapes ? _largeItemShapes[_dscItemShapeMap[itm->icon]] : (_dscItemShapeMap[itm->icon] < 15 ? 0 : _smallItemShapes[_dscItemShapeMap[itm->icon] - 15]), scaleSteps); + x = x + (itemPosFin[o & 7] << 1) - ((shp[2] << 3) >> 1); + y -= shp[1]; + + if (itm->pos != 8) + y += itemPosFin[(o >> 1) & 7]; + + drawBlockObject(0, 2, shp, x, y, 5); + _screen->setShapeFadingLevel(0); + } + } + + o = itm->next; + forceLoop = false; + if (tile2 != -1) + setLevelShapesDim(index, _shpDmX1, _shpDmX2, 5); + } +} + +void EoBCoreEngine::drawDoor(int index) { + int s = _visibleBlocks[index]->walls[_sceneDrawVarDown]; + + if (_flags.gameID == GI_EOB1 && s == 0x85) + s = 0; + + if (s >= _dscDoorShpIndexSize) + return; + + int type = _dscDoorShpIndex[s]; + int d = _dscDimMap[index]; + int w = _dscShapeCoords[(index * 5 + 4) << 1]; + + int x = 88 + w; + int y = 0; + + int16 y1 = 0; + int16 y2 = 0; + setDoorShapeDim(index, y1, y2, 5); + drawDoorIntern(type, index, x, y, w, s, d, y1, y2); + drawLevelModifyScreenDim(5, _shpDmX1, 0, _shpDmX2, 15); +} + +void EoBCoreEngine::drawMonsters(int index) { + static const uint8 distMap[] = { 2, 1, 0, 4 }; + static const uint8 yAdd[] = { 20, 12, 4, 4, 2, 0, 0 }; + + int blockDistance = distMap[_dscDimMap[index]]; + + uint16 bl = _visibleBlockIndex[index]; + if (!bl) + return; + + int drawObjDirIndex = _currentDirection * 5; + int cDirOffs = _currentDirection << 2; + + EoBMonsterInPlay *drawObj[5]; + memset(drawObj, 0, 5 * sizeof(EoBMonsterInPlay *)); + + for (int i = 0; i < 30; i++) { + if (_monsters[i].block != bl) + continue; + drawObj[_drawObjPosIndex[drawObjDirIndex + _monsters[i].pos]] = &_monsters[i]; + } + + for (int i = 0; i < 5; i++) { + EoBMonsterInPlay *d = drawObj[i]; + if (!d) + continue; + + EoBMonsterProperty *p = &_monsterProps[d->type]; + + if (_flags.gameID == GI_EOB2 && (p->capsFlags & 0x100) && !(_partyEffectFlags & 0x220) && !(d->flags & 2)) + continue; + + int f = (d->animStep << 4) + cDirOffs + d->dir; + f = (p->capsFlags & 2) ? _monsterFrmOffsTable1[f] : _monsterFrmOffsTable2[f]; + + if (!blockDistance && d->curAttackFrame < 0) + f = d->curAttackFrame + 7; + + int subFrame = ABS(f); + int shpIndex = d->shpIndex ? 18 : 0; + int palIndex = d->palette ? ((((shpIndex == 18) ? subFrame + 5 : subFrame - 1) << 1) + (d->palette - 1)) : -1; + + const uint8 *shp = _screen->scaleShape(_monsterShapes[subFrame + shpIndex - 1], blockDistance); + + int v30 = (subFrame == 1 || subFrame > 3) ? 1 : 0; + int v1e = (d->pos == 4) ? 4 : _dscItemPosIndex[cDirOffs + d->pos]; + int posIndex = (index * 5 + v1e) << 1; + + int x = _dscShapeCoords[posIndex] + 88; + int y = _dscShapeCoords[posIndex + 1] + 127; + + if (p->u30 == 1) { + if (v30) { + if (_flags.gameID == GI_EOB2) + posIndex = ((posIndex >> 1) - v1e) << 1; + y = _dscShapeCoords[posIndex + 1] + 127 + yAdd[blockDistance + ((v1e == 4 || _flags.gameID == GI_EOB1) ? 0 : 3)]; + } else { + if (_flags.gameID == GI_EOB2) + posIndex = ((posIndex >> 1) - v1e + 4) << 1; + x = _dscShapeCoords[posIndex] + 88; + } + } + + int w = shp[2] << 3; + int h = shp[1]; + + x = x - (w >> 1) + (d->idleAnimState >> 4); + y = y - h + (d->idleAnimState & 0x0F); + + drawMonsterShape(shp, x, y, f >= 0 ? 0 : 1, d->flags, palIndex); + + if (_flags.gameID == GI_EOB1) { + _screen->setShapeFadingLevel(0); + continue; + } + + for (int ii = 0; ii < 3; ii++) { + if (!p->decorations[ii]) + continue; + + SpriteDecoration *dcr = &_monsterDecorations[(p->decorations[ii] - 1) * 6 + subFrame + shpIndex - 1]; + + if (!dcr->shp) + continue; + + shp = _screen->scaleShape(dcr->shp, blockDistance); + int dx = dcr->x; + int dy = dcr->y; + + for (int iii = 0; iii < blockDistance; iii++) { + dx = (dx << 1) / 3; + dy = (dy << 1) / 3; + } + + drawMonsterShape(shp, x + ((f < 0) ? (w - dx - (shp[2] << 3)) : dx), y + dy, f >= 0 ? 0 : 1, d->flags, -1); + } + _screen->setShapeFadingLevel(0); + } +} + +void EoBCoreEngine::drawWallOfForce(int index) { + int d = _dscDimMap[index]; + assert(d < 3); + int dH = _wallOfForceDsNumH[d]; + int dW = _wallOfForceDsNumW[d]; + int y = _wallOfForceDsY[d]; + int shpId = _wallOfForceShpId[d] + _teleporterPulse; + int h = _wallOfForceShapes[shpId][1]; + int w = _wallOfForceShapes[shpId][2] << 3; + + for (int i = 0; i < dH; i++) { + int x = _wallOfForceDsX[index]; + for (int ii = 0; ii < dW; ii++) { + drawBlockObject(0, 2, _wallOfForceShapes[shpId], x, y, 5); + x += w; + } + y += h; + shpId ^= 1; + } +} + +void EoBCoreEngine::drawFlyingObjects(int index) { + LevelBlockProperty *bl = _visibleBlocks[index]; + int blockIndex = _visibleBlockIndex[index]; + int w = bl->walls[_sceneDrawVarDown]; + + if (_wllVmpMap[w] && !(_wllWallFlags[w] & 0x80)) + return; + + EoBFlyingObject *drawObj[5]; + memset(drawObj, 0, 5 * sizeof(EoBFlyingObject *)); + + for (int i = 0; i < 10; i++) { + if (!_flyingObjects[i].enable || blockIndex != _flyingObjects[i].curBlock) + continue; + drawObj[_drawObjPosIndex[_currentDirection * 5 + (_flyingObjects[i].curPos & 3)]] = &_flyingObjects[i]; + } + + for (int i = 0; i < 5; i++) { + EoBFlyingObject *fo = drawObj[i]; + if (!fo) + continue; + + int p = _dscItemPosIndex[(_currentDirection << 2) + (fo->curPos & 3)]; + int x = _dscShapeCoords[(index * 5 + p) << 1] + 88; + int y = 39; + + int sclValue = _flightObjSclIndex[(index << 2) + p]; + int flipped = 0; + + if (sclValue < 0) { + _screen->setShapeFadingLevel(0); + continue; + } + + const uint8 *shp = 0; + bool noFade = false; + + if (fo->enable == 1) { + int shpIx = _dscItemShapeMap[_items[fo->item].icon]; + int dirOffs = (fo->direction == _currentDirection) ? 0 : ((fo->direction == (_currentDirection ^ 2)) ? 1 : -1); + + if (dirOffs == -1 || _flightObjShpMap[shpIx] == -1) { + shp = shpIx < _numLargeItemShapes ? _largeItemShapes[shpIx] : (shpIx < 15 ? 0 : _smallItemShapes[shpIx - 15]); + flipped = fo->direction == ((_currentDirection + 1) & 3) ? 1 : 0; + } else { + shp = (_flightObjShpMap[shpIx] + dirOffs) < _numThrownItemShapes ? _thrownItemShapes[_flightObjShpMap[shpIx] + dirOffs] : _spellShapes[_flightObjShpMap[shpIx - _numThrownItemShapes] + dirOffs]; + flipped = _flightObjFlipIndex[(fo->direction << 2) + (fo->curPos & 3)]; + } + + } else { + noFade = true; + shp = (fo->objectType < _numThrownItemShapes) ? _thrownItemShapes[fo->objectType] : _spellShapes[fo->objectType - _numThrownItemShapes]; + flipped = _flightObjFlipIndex[(fo->direction << 2) + (fo->curPos & 3)]; + + if (fo->flags & 0x40) { + x = _dscShapeCoords[(index * 5 + 4) << 1] + 88; + y = 44; + } + } + + assert(shp); + + shp = _screen->scaleShape(shp, sclValue); + + if (noFade) { + _screen->setShapeFadingLevel(0); + noFade = false; + } + + x -= (shp[2] << 2); + y -= (y == 44 ? (shp[1] >> 1) : shp[1]); + + drawBlockObject(flipped, 2, shp, x, y, 5); + _screen->setShapeFadingLevel(0); + } +} + +void EoBCoreEngine::drawTeleporter(int index) { + static const uint8 telprtX[] = { 0x28, 0x1C, 0x12 }; + static const uint8 telprtY[] = { 0x0D, 0x15, 0x1A }; + + int t = 2 - _dscDimMap[index]; + if (t < 0) + return; + + int16 x1 = _dscItemShpX[index] - telprtX[t]; + int16 y1 = telprtY[t]; + + for (int i = 0; i < 2; i++) { + + int16 x2 = 0; + int16 y2 = 0; + int d = (t << 1) + i; + if (!d) + x2 = y2 = -4; + + const uint8 *shp = _teleporterShapes[d ^ _teleporterPulse]; + + for (int ii = 0; ii < 13; ii++) + drawBlockObject(0, 2, shp, x1 + x2 + _teleporterShapeCoords[d * 26 + ii * 2], y1 + y2 + _teleporterShapeCoords[d * 26 + ii * 2 + 1], 5); + } +} + +void EoBCoreEngine::updateMonsters(int unit) { + for (int i = 0; i < 30; i++) { + EoBMonsterInPlay *m = &_monsters[i]; + if (m->unit == unit) { + if (m->hitPointsCur <= 0 || m->flags & 0x20) + continue; + if (m->directionChanged) { + m->directionChanged = 0; + continue; + } + + updateMonsterDest(m); + + if (m->mode > 0) + updateMonsterAttackMode(m); + + switch (m->mode) { + case 0: + updateMoveMonster(m); + break; + case 1: + updateMonsterFollowPath(m, 2); + break; + case 2: + updateMonsterFollowPath(m, -1); + break; + case 3: + updateMonsterFollowPath(m, 1); + break; + case 5: + updateMonstersStraying(m, -1); + break; + case 6: + updateMonstersStraying(m, 1); + break; + case 7: + case 10: + updateMonstersSpellStatus(m); + break; + default: + break; + } + + if (m->mode != 4 && m->mode != 7 && m->mode != 8) + m->animStep ^= 1; + + if (_monsterProps[m->type].u30 == 1) + setBlockMonsterDirection(m->block, m->dir); + } + } + checkFlyingObjects(); +} + +void EoBCoreEngine::updateMonsterDest(EoBMonsterInPlay *m) { + if (m->mode >= 7 && m->mode <= 10) + return; + int dist = getBlockDistance(m->block, _currentBlock); + if (dist >= 4) + return; + + int s = getNextMonsterDirection(m->block, _currentBlock) - (m->dir << 1) - 3; + + if (s < 0) + s += 8; + + if (s <= 2 && dist >= 2) + return; + + m->mode = 0; + m->dest = _currentBlock; +} + +void EoBCoreEngine::updateMonsterAttackMode(EoBMonsterInPlay *m) { + if (!(m->flags & 1) || m->mode == 10) + return; + if (m->mode == 8) { + turnFriendlyMonstersHostile(); + return; + } + m->mode = 0; + m->dest = _currentBlock; +} + +void EoBCoreEngine::updateAllMonsterDests() { + for (int i = 0; i < 30; i++) + updateMonsterDest(&_monsters[i]); +} + +void EoBCoreEngine::turnFriendlyMonstersHostile() { + EoBMonsterInPlay *m = 0; + for (int i = 0; i < 30; i++) { + if (_monsters[i].mode == 8) { + _monsters[i].mode = 0; + _monsters[i].dest = _currentBlock; + m = &_monsters[i]; + } + } + + if (m) { + if (m->type == 7) + setScriptFlags(0x40000); + else if (m->type == 12) + setScriptFlags(0x8000000); + } +} + +int EoBCoreEngine::getNextMonsterDirection(int curBlock, int destBlock) { + uint8 c = destBlock % 32; + uint8 d = destBlock / 32; + uint8 e = curBlock % 32; + uint8 f = curBlock / 32; + + int r = 0; + + int s1 = f - d; + int d1 = ABS(s1); + s1 <<= 1; + int s2 = c - e; + int d2 = ABS(s2); + s2 <<= 1; + + if (s1 >= d2) + r |= 8; + if (-s1 >= d2) + r |= 4; + if (s2 >= d1) + r |= 2; + if (-s2 >= d1) + r |= 1; + + return _monsterDirChangeTable[r]; +} + +int EoBCoreEngine::getNextMonsterPos(EoBMonsterInPlay *m, int block) { + if ((_flags.gameID == GI_EOB1 && _monsterProps[m->type].u30 != 0) || (_flags.gameID == GI_EOB2 && _monsterProps[m->type].u30 == 2)) + return -1; + int d = findFreeMonsterPos(block, _monsterProps[m->type].u30); + if (d < 0) + return -1; + + int dir = m->dir; + if (_flags.gameID == GI_EOB2) { + if (_monsterProps[m->type].u30 == 1) { + if (d == 9) + return -1; + + int v = _monsterCloseAttUnkTable[d]; + if (v != -1) + ////// + m->dir = 0; + return v; + } + } else { + dir &= 1; + } + + for (int i = 0; i < 4; i++) { + int v = m->dir ^ _monsterCloseAttPosTable2[(dir << 2) + i]; + if (!(d & (1 << v))) + return v; + } + + return -1; +} + +int EoBCoreEngine::findFreeMonsterPos(int block, int size) { + int nm = _levelBlockProperties[block].flags & 7; + if (nm == 4) + return -2; + + int res = 0; + + for (int i = 0; i < 30; i++) { + EoBMonsterInPlay *m = &_monsters[i]; + if (m->block != block) + continue; + if (_monsterProps[m->type].u30 != size) + return -1; + + if (m->pos == 4 && !(_flags.gameID == GI_EOB2 && m->flags & 0x20)) + m->pos = (_flags.gameID == GI_EOB2 && _monsterProps[m->type].u30 == 1) ? 0 : _monsterCloseAttPosTable1[m->dir]; + + res |= (1 << m->pos); + if (--nm == 0) + break; + } + + return res; +} + +void EoBCoreEngine::updateMoveMonster(EoBMonsterInPlay *m) { + EoBMonsterProperty *p = &_monsterProps[m->type]; + int d = getNextMonsterDirection(m->block, _currentBlock); + + if ((_flags.gameID == GI_EOB2) && (p->capsFlags & 0x800) && !(d & 1)) + d >>= 1; + else + d = m->dir; + + d = calcNewBlockPosition(m->block, d); + + if (m->dest == d && _currentBlock != d) { + m->mode = rollDice(1, 2, -1) + 5; + return; + } + + if (updateMonsterTryDistanceAttack(m)) + return; + + if (updateMonsterTryCloseAttack(m, d)) + return; + + m->curAttackFrame = 0; + walkMonster(m, m->dest); + + if (p->capsFlags & 8) + updateMonsterTryCloseAttack(m, -1); +} + +bool EoBCoreEngine::updateMonsterTryDistanceAttack(EoBMonsterInPlay *m) { + EoBMonsterProperty *p = &_monsterProps[m->type]; + if (!m->numRemoteAttacks || ((_flags.gameID == GI_EOB1) && !(p->capsFlags & 0x40))) + return false; + + if ((_flags.gameID == GI_EOB1 && m->stepsTillRemoteAttack < 5) || (_flags.gameID == GI_EOB2 && (rollDice(1, 3) > m->stepsTillRemoteAttack))) { + m->stepsTillRemoteAttack++; + return false; + } + + if (getBlockDistance(m->block, _currentBlock) > 3 || getNextMonsterDirection(m->block, _currentBlock) != (m->dir << 1)) + return false; + + int d = m->dir; + int bl = calcNewBlockPosition(m->block, d); + + while (bl != _currentBlock) { + if (!(_wllWallFlags[_levelBlockProperties[bl].walls[d ^ 2]] & 3) || (_levelBlockProperties[bl].flags & 7)) + return false; + bl = calcNewBlockPosition(bl, d); + } + + Item itm = 0; + if (_flags.gameID == GI_EOB1) { + switch (m->type - 4) { + case 0: + launchMagicObject(-1, 9, m->block, m->pos, m->dir); + snd_processEnvironmentalSoundEffect(31, m->block); + break; + case 10: + launchMagicObject(-1, _enemyMageSpellList[m->numRemoteAttacks], m->block, m->pos, m->dir); + snd_processEnvironmentalSoundEffect(_enemyMageSfx[m->numRemoteAttacks], m->block); + break; + case 11: + itm = duplicateItem(60); + if (itm) { + if (!launchObject(-1, itm, m->block, m->pos, m->dir, _items[itm].type)) + _items[itm].block = -1; + } + break; + case 12: + launchMagicObject(-1, 0, m->block, m->pos, m->dir); + snd_processEnvironmentalSoundEffect(85, m->block); + break; + case 13: + snd_processEnvironmentalSoundEffect(83, m->block); + _txt->printMessage(_monsterSpecAttStrings[1]); + for (int i = 0; i < 6; i++) + statusAttack(i, 4, _monsterSpecAttStrings[2], 1, 5, 9, 1); + break; + case 17: + d = rollDice(1, 4, -1); + if (d >= 3) { + for (int i = 0; i < 6; i++) { + if (!testCharacter(i, 3)) + continue; + _txt->printMessage(_monsterSpecAttStrings[0], -1, _characters[i].name); + inflictCharacterDamage(i, rollDice(2, 8, 1)); + } + snd_processEnvironmentalSoundEffect(108, m->block); + } else { + launchMagicObject(-1, _beholderSpellList[d], m->block, m->pos, m->dir); + snd_processEnvironmentalSoundEffect(_beholderSfx[d], m->block); + } + break; + default: + break; + } + + } else { + int cw = 0; + if (p->remoteWeaponChangeMode == 1) { + cw = m->curRemoteWeapon++; + if (m->curRemoteWeapon == p->numRemoteWeapons) + m->curRemoteWeapon = 0; + } else if (p->remoteWeaponChangeMode == 2) { + cw = rollDice(1, p->numRemoteWeapons, -1); + } + + int s = p->remoteWeapons[cw]; + if (s >= 0) { + if (s < 20) { + monsterSpellCast(m, s); + } else if (s == 20) { + snd_processEnvironmentalSoundEffect(103, m->block); + _txt->printMessage(_monsterSpecAttStrings[0]); + for (int i = 0; i < 6; i++) + statusAttack(i, 4, _monsterSpecAttStrings[1], 1, 5, 9, 1); + } + } else { + itm = duplicateItem(-s); + if (itm) { + if (!launchObject(-1, itm, m->block, m->pos, m->dir, _items[itm].type)) + _items[itm].block = -1; + } + } + } + + if (m->numRemoteAttacks != 255) + m->numRemoteAttacks--; + m->stepsTillRemoteAttack = 0; + return true; +} + +bool EoBCoreEngine::updateMonsterTryCloseAttack(EoBMonsterInPlay *m, int block) { + if (block == -1) + block = calcNewBlockPosition(m->block, m->dir); + + if (block != _currentBlock) + return false; + + int r = (m->pos == 4 || (_flags.gameID == GI_EOB2 && _monsterProps[m->type].u30 == 1)) ? 1 : _monsterCloseAttChkTable1[(m->dir << 2) + m->pos]; + + if (r) { + m->flags ^= 4; + if (!(m->flags & 4)) + return true; + + bool facing = (m->block == _visibleBlockIndex[13]); + + if (facing) { + disableSysTimer(2); + if (m->type == 4) + updateEnvironmentalSfx(_monsterProps[m->type].sound1); + m->curAttackFrame = -2; + _flashShapeTimer = 0; + drawScene(1); + m->curAttackFrame = -1; + if (m->type != 4) + updateEnvironmentalSfx(_monsterProps[m->type].sound1); + _flashShapeTimer = _system->getMillis() + 8 * _tickLength; + drawScene(1); + } else { + updateEnvironmentalSfx(_monsterProps[m->type].sound1); + } + + monsterCloseAttack(m); + + if (facing) { + m->curAttackFrame = 0; + m->animStep ^= 1; + _sceneUpdateRequired = 1; + enableSysTimer(2); + _flashShapeTimer = _system->getMillis() + 8 * _tickLength; + } + } else { + int b = m->block; + if ((_levelBlockProperties[b].flags & 7) == 1) { + m->pos = 4; + } else { + b = getNextMonsterPos(m, b); + if (b >= 0) + m->pos = b; + } + checkSceneUpdateNeed(m->block); + } + + return true; +} + +void EoBCoreEngine::walkMonster(EoBMonsterInPlay *m, int destBlock) { + if (++_monsterStepCounter > 10) { + _monsterStepCounter = 0; + _monsterStepMode ^= 1; + } + + const int8 *tbl = _monsterStepMode ? _monsterStepTable3 : _monsterStepTable2; + + int s = m->dir << 1; + int b = m->block; + int d = getNextMonsterDirection(b, destBlock); + if (d == -1) + return; + + if (m->flags & 8) { + // Interestingly, the fear spell in EOB 1 does not expire. + // I don't know whether this is intended or not. + if (_flags.gameID == GI_EOB1) { + d ^= 4; + } else if (m->spellStatusLeft > 0) { + if (--m->spellStatusLeft == 0) + m->flags &= ~8; + else + d ^= 4; + } + } + + int d2 = (d - s) & 7; + + if (_flags.gameID == GI_EOB1) { + if ((b + _monsterStepTable0[d >> 1] == _currentBlock) && !(d & 1)) { + if (d2 >= 5) + s = m->dir - 1; + else if (d2 != 0) + s = m->dir + 1; + walkMonsterNextStep(m, -1, s & 3); + return; + } + } else if (_flags.gameID == GI_EOB2) { + if (b + _monsterStepTable0[d] == destBlock) { + if (d & 1) { + int e = _monsterStepTable1[((d - 1) << 1) + m->dir]; + if (e && (!(_monsterProps[m->type].capsFlags & 0x200) || (rollDice(1, 4) < 4))) { + if (walkMonsterNextStep(m, b + e, -1)) + return; + } + } else { + walkMonsterNextStep(m, -1, d >> 1); + return; + } + } + } + + if (d2) { + if (d2 >= 5) + s -= (1 + ((d & 1) ^ 1)); + else + s += (1 + ((d & 1) ^ 1)); + s &= 7; + } + + for (int i = 7; i > -1; i--) { + s = (s + tbl[i]) & 7; + uint16 b2 = (s & 1) ? 0 : calcNewBlockPosition(b, s >> 1); + if (!b2) + continue; + if (walkMonsterNextStep(m, b2, s >> 1)) + return; + } +} + +bool EoBCoreEngine::walkMonsterNextStep(EoBMonsterInPlay *m, int destBlock, int direction) { + EoBMonsterProperty *p = &_monsterProps[m->type]; + int obl = m->block; + + if (destBlock != m->block && destBlock != -1) { + if (m->flags & 8) { + if (getBlockDistance(destBlock, _currentBlock) < getBlockDistance(m->block, _currentBlock)) + return false; + } + + if (destBlock == _currentBlock) + return false; + + if (direction == -1) + direction = m->dir; + + LevelBlockProperty *l = &_levelBlockProperties[destBlock]; + uint8 w = l->walls[direction ^ 2]; + + if (!(_wllWallFlags[w] & 4)) { + if (_flags.gameID == GI_EOB1 || !(p->capsFlags & 0x1000) || _wllShapeMap[w] != -1) + return false; + + if (_wllWallFlags[w] & 0x20) { + if (p->capsFlags & 4 && m->type == 1) + l->walls[direction] = l->walls[direction ^ 2] = 72; + else + openDoor(destBlock); + } + + if (direction != -1) { + m->dir = direction; + checkSceneUpdateNeed(m->block); + } + return true; + } + + if ((l->flags & 7) && destBlock) { + int pos = getNextMonsterPos(m, destBlock); + if (pos == -1) + return false; + m->pos = pos; + } + + placeMonster(m, destBlock, direction); + direction = -1; + } + + if (direction != -1) + m->dir = direction; + + checkSceneUpdateNeed(obl); + if (!_partyResting && p->sound2 > 0) + snd_processEnvironmentalSoundEffect(p->sound2, m->block); + + return true; +} + +void EoBCoreEngine::updateMonsterFollowPath(EoBMonsterInPlay *m, int turnSteps) { + if (!walkMonsterNextStep(m, calcNewBlockPosition(m->block, m->dir), -1)) { + m->dir = (m->dir + turnSteps) & 3; + walkMonsterNextStep(m, -1, m->dir); + } +} + +void EoBCoreEngine::updateMonstersStraying(EoBMonsterInPlay *m, int a) { + if (m->f_9 >= 0) { + if (m->f_9 == 0) + updateMonsterFollowPath(m, -a); + + int8 d = (m->dir + a) & 3; + uint16 bl = calcNewBlockPosition(m->block, d); + uint8 flg = _wllWallFlags[_levelBlockProperties[bl].walls[_dscBlockMap[d]]] & 4; + + if (m->f_9 == 0) { + if (!flg) + m->f_9 = -1; + return; + } + + if (flg) { + walkMonsterNextStep(m, -1, d); + m->f_9 = -1; + return; + } + } + + if (walkMonsterNextStep(m, calcNewBlockPosition(m->block, m->dir), -1)) { + m->f_9 = 1; + } else { + walkMonsterNextStep(m, -1, (m->dir - a) & 3); + m->f_9 = 0; + } +} + +void EoBCoreEngine::updateMonstersSpellStatus(EoBMonsterInPlay *m) { + if (m->spellStatusLeft) { + if (!--m->spellStatusLeft) + m->mode = 0; + } +} + +void EoBCoreEngine::setBlockMonsterDirection(int block, int dir) { + for (int i = 0; i < 30; i++) { + if (_monsters[i].block != block || _monsters[i].dir == dir) + continue; + _monsters[i].dir = dir; + _monsters[i].directionChanged = 1; + } +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/sprites_lol.cpp b/engines/kyra/engine/sprites_lol.cpp new file mode 100644 index 0000000000..910447c45a --- /dev/null +++ b/engines/kyra/engine/sprites_lol.cpp @@ -0,0 +1,1558 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/engine/lol.h" +#include "kyra/graphics/screen_lol.h" + +namespace Kyra { + +void LoLEngine::loadMonsterShapes(const char *file, int monsterIndex, int animType) { + releaseMonsterShapes(monsterIndex); + _screen->loadBitmap(file, 3, 3, 0); + + const uint8 *p = _screen->getCPagePtr(2); + const uint8 *ts[16]; + + for (int i = 0; i < 16; i++) { + ts[i] = _screen->getPtrToShape(p, i); + + bool replaced = false; + int pos = monsterIndex << 4; + + for (int ii = 0; ii < i; ii++) { + if (ts[i] != ts[ii]) + continue; + + _monsterShapes[pos + i] = _monsterShapes[pos + ii]; + replaced = true; + break; + } + + if (!replaced) + _monsterShapes[pos + i] = _screen->makeShapeCopy(p, i); + + int size = _screen->getShapePaletteSize(_monsterShapes[pos + i]) << 3; + _monsterPalettes[pos + i] = new uint8[size]; + memset(_monsterPalettes[pos + i], 0, size); + } + + for (int i = 0; i < 4; i++) { + for (int ii = 0; ii < 16; ii++) { + uint8 **of = &_monsterDecorationShapes[monsterIndex * 192 + i * 48 + ii * 3]; + int s = (i << 4) + ii + 17; + of[0] = _screen->makeShapeCopy(p, s); + of[1] = _screen->makeShapeCopy(p, s + 1); + of[2] = _screen->makeShapeCopy(p, s + 2); + } + } + _monsterAnimType[monsterIndex] = animType & 0xFF; + + uint8 *palShape = _screen->makeShapeCopy(p, 16); + + _screen->clearPage(3); + _screen->drawShape(2, palShape, 0, 0, 0, 0); + + uint8 *tmpPal1 = new uint8[64]; + uint8 *tmpPal2 = new uint8[256]; + uint16 *tmpPal3 = new uint16[256]; + memset(tmpPal1, 0, 64); + + for (int i = 0; i < 64; i++) { + tmpPal1[i] = *p; + p += 320; + } + + p = _screen->getCPagePtr(2); + + for (int i = 0; i < 16; i++) { + int pos = (monsterIndex << 4) + i; + uint16 sz = MIN(_screen->getShapeSize(_monsterShapes[pos]) - 10, 256); + memset(tmpPal2, 0, 256); + memcpy(tmpPal2, _monsterShapes[pos] + 10, sz); + memset(tmpPal3, 0xFF, 256 * sizeof(uint16)); + uint8 numCol = *tmpPal2; + + for (int ii = 0; ii < numCol; ii++) { + uint8 *cl = (uint8 *)memchr(tmpPal1, tmpPal2[1 + ii], 64); + if (!cl) + continue; + tmpPal3[ii] = (uint16)(cl - tmpPal1); + } + + for (int ii = 0; ii < 8; ii++) { + memset(tmpPal2, 0, 256); + memcpy(tmpPal2, _monsterShapes[pos] + 10, sz); + for (int iii = 0; iii < numCol; iii++) { + if (tmpPal3[iii] == 0xFFFF) + continue; + if (p[tmpPal3[iii] * 320 + ii + 1]) + tmpPal2[1 + iii] = p[tmpPal3[iii] * 320 + ii + 1]; + } + memcpy(_monsterPalettes[pos] + ii * numCol, &tmpPal2[1], numCol); + } + } + + delete[] tmpPal1; + delete[] tmpPal2; + delete[] tmpPal3; + delete[] palShape; +} + +void LoLEngine::releaseMonsterShapes(int monsterIndex) { + for (int i = 0; i < 16; i++) { + int pos = (monsterIndex << 4) + i; + int pos2 = (monsterIndex << 4) + 16; + if (_monsterShapes[pos]) { + uint8 *t = _monsterShapes[pos]; + delete[] _monsterShapes[pos]; + for (int ii = pos; ii < pos2; ii++) { + if (_monsterShapes[ii] == t) + _monsterShapes[ii] = 0; + } + } + + if (_monsterPalettes[pos]) { + delete[] _monsterPalettes[pos]; + _monsterPalettes[pos] = 0; + } + } + + for (int i = 0; i < 192; i++) { + int pos = (monsterIndex * 192) + i; + if (_monsterDecorationShapes[pos]) { + delete[] _monsterDecorationShapes[pos]; + _monsterDecorationShapes[pos] = 0; + } + } +} + +int LoLEngine::deleteMonstersFromBlock(int block) { + int i = _levelBlockProperties[block].assignedObjects; + int cnt = 0; + uint16 next = 0; + + while (i) { + next = findObject(i)->nextAssignedObject; + if (!(i & 0x8000)) { + i = next; + continue; + } + + LoLMonster *m = &_monsters[i & 0x7FFF]; + + cnt++; + setMonsterMode(m, 14); + + checkSceneUpdateNeed(m->block); + + placeMonster(m, 0, 0); + + i = next; + } + return cnt; +} + +void LoLEngine::setMonsterMode(LoLMonster *monster, int mode) { + if (monster->mode == 13 && mode != 14) + return; + + if (mode == 7) { + monster->destX = _partyPosX; + monster->destY = _partyPosY; + } + + if (monster->mode == 1 && mode == 7) { + for (int i = 0; i < 30; i++) { + if (monster->mode != 1) + continue; + monster->mode = mode; + monster->fightCurTick = 0; + monster->destX = _partyPosX; + monster->destY = _partyPosY; + setMonsterDirection(monster, calcMonsterDirection(monster->x, monster->y, monster->destX, monster->destY)); + } + } else { + monster->mode = mode; + monster->fightCurTick = 0; + if (mode == 14) + monster->hitPoints = 0; + if (mode == 13 && (monster->flags & 0x20)) { + monster->mode = 0; + monsterDropItems(monster); + if (_currentLevel != 29) + setMonsterMode(monster, 14); + runLevelScriptCustom(0x404, -1, monster->id, monster->id, 0, 0); + checkSceneUpdateNeed(monster->block); + if (monster->mode == 14) + placeMonster(monster, 0, 0); + } + } +} + +bool LoLEngine::updateMonsterAdjustBlocks(LoLMonster *monster) { + static const uint8 dims[] = { 0, 13, 9, 3 }; + if (monster->properties->flags & 8) + return true; + + uint16 x1 = (monster->x & 0xFF00) | 0x80; + uint16 y1 = (monster->y & 0xFF00) | 0x80; + int x2 = _partyPosX; + int y2 = _partyPosY; + + uint16 dir = 0; + if (monster->properties->flags & 1) { + dir = monster->direction >> 1; + } else { + dir = calcMonsterDirection(x1, y1, x2, y2); + if ((monster->properties->flags & 2) && (dir == (monster->direction ^ 4))) + return false; + dir >>= 1; + } + + calcSpriteRelPosition(x1, y1, x2, y2, dir); + x2 >>= 8; + y2 >>= 8; + + if (y2 < 0 || y2 > 3) + return false; + + int t = (x2 < 0) ? -x2 : x2; + if (t > y2) + return false; + + for (int i = 0; i < 18; i++) + _visibleBlocks[i] = &_levelBlockProperties[(monster->block + _dscBlockIndex[dir + i]) & 0x3FF]; + + int16 fx1 = 0; + int16 fx2 = 0; + setLevelShapesDim(x2 + dims[y2], fx1, fx2, 13); + + return fx1 < fx2; +} + +void LoLEngine::placeMonster(LoLMonster *monster, uint16 x, uint16 y) { + bool cont = true; + int t = monster->block; + if (monster->block) { + removeAssignedObjectFromBlock(&_levelBlockProperties[t], ((uint16)monster->id) | 0x8000); + _levelBlockProperties[t].direction = 5; + checkSceneUpdateNeed(t); + } else { + cont = false; + } + + monster->block = calcBlockIndex(x, y); + + if (monster->x != x || monster->y != y) { + monster->x = x; + monster->y = y; + monster->currentSubFrame = (monster->currentSubFrame + 1) & 3; + } + + if (monster->block == 0) + return; + + assignObjectToBlock(&_levelBlockProperties[monster->block].assignedObjects, ((uint16)monster->id) | 0x8000); + _levelBlockProperties[monster->block].direction = 5; + checkSceneUpdateNeed(monster->block); + + // WORKAROUND: Some monsters in the white tower have sound id's of 0xFF. This is definitely a bug, since the + // last valid track number is 249 and there is no specific handling for 0xFF. Nonetheless this wouldn't + // cause problems in the original code, because it just so happens that the invalid memory address points + // to an entry in _ingameGMSoundIndex which just so happens to have a value of -1 + if (monster->properties->sounds[0] == 0 || monster->properties->sounds[0] == 255 || cont == false) + return; + + if ((!(monster->properties->flags & 0x100) || ((monster->currentSubFrame & 1) == 0)) && monster->block == t) + return; + + if (monster->block != t) + runLevelScriptCustom(monster->block, 0x800, -1, monster->id, 0, 0); + + if (_updateFlags & 1) + return; + + snd_processEnvironmentalSoundEffect(monster->properties->sounds[0], monster->block); +} + +int LoLEngine::calcMonsterDirection(uint16 x1, uint16 y1, uint16 x2, uint16 y2) { + int16 r = 0; + + int16 t1 = y1 - y2; + if (t1 < 0) { + r++; + t1 = -t1; + } + + r <<= 1; + + int16 t2 = x2 - x1; + if (t2 < 0) { + r++; + t2 = -t2; + } + + uint8 f = (t1 > t2) ? 1 : 0; + + if (t2 >= t1) + SWAP(t1, t2); + + r = (r << 1) | f; + + t1 = (t1 + 1) >> 1; + + f = (t1 > t2) ? 1 : 0; + r = (r << 1) | f; + + static const uint8 retVal[] = { 1, 2, 1, 0, 7, 6, 7, 0, 3, 2, 3, 4, 5, 6, 5, 4}; + return retVal[r]; +} + +void LoLEngine::setMonsterDirection(LoLMonster *monster, int dir) { + monster->direction = dir; + + if (!(dir & 1) || ((monster->direction - (monster->facing << 1)) >= 2)) + monster->facing = monster->direction >> 1; + + checkSceneUpdateNeed(monster->block); +} + +void LoLEngine::monsterDropItems(LoLMonster *monster) { + uint16 a = monster->assignedItems; + while (a) { + uint16 b = a; + a = _itemsInPlay[a].nextAssignedObject; + setItemPosition(b, monster->x, monster->y, 0, 1); + } +} + +int LoLEngine::checkBlockBeforeObjectPlacement(uint16 x, uint16 y, uint16 objectWidth, uint16 testFlag, uint16 wallFlag) { + _objectLastDirection = 0; + uint16 x2 = 0; + uint16 y2 = 0; + int xOffs = 0; + int yOffs = 0; + int flag = 0; + + int r = testBlockPassability(calcBlockIndex(x, y), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x, y, testFlag); + if (r) + return 4; + + if (x & 0x80) { + if (((x & 0xFF) + objectWidth) & 0xFF00) { + xOffs = 1; + _objectLastDirection = 2; + x2 = x + objectWidth; + + r = testBlockPassability(calcBlockIndex(x2, y), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x + xOffs, y, testFlag); + if (r) + return 4; + + flag = 1; + } + } else { + if (((x & 0xFF) - objectWidth) & 0xFF00) { + xOffs = -1; + _objectLastDirection = 6; + x2 = x - objectWidth; + + r = testBlockPassability(calcBlockIndex(x2, y), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x + xOffs, y, testFlag); + if (r) + return 4; + + flag = 1; + } + } + + if (y & 0x80) { + if (((y & 0xFF) + objectWidth) & 0xFF00) { + yOffs = 1; + _objectLastDirection = 4; + y2 = y + objectWidth; + + r = testBlockPassability(calcBlockIndex(x, y2), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x, y + yOffs, testFlag); + if (r) + return 4; + flag &= 1; + } else { + flag = 0; + } + } else { + if (((y & 0xFF) - objectWidth) & 0xFF00) { + yOffs = -1; + _objectLastDirection = 0; + y2 = y - objectWidth; + + r = testBlockPassability(calcBlockIndex(x, y2), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x, y + yOffs, testFlag); + if (r) + return 4; + flag &= 1; + } else { + flag = 0; + } + } + + if (!flag) + return 0; + + r = testBlockPassability(calcBlockIndex(x2, y2), x, y, objectWidth, testFlag, wallFlag); + if (r) + return r; + + r = checkBlockOccupiedByParty(x + xOffs, y + yOffs, testFlag); + if (r) + return 4; + + return 0; +} + +int LoLEngine::testBlockPassability(int block, int x, int y, int objectWidth, int testFlag, int wallFlag) { + if (block == _currentBlock) + testFlag &= 0xFFFE; + + if (testFlag & 1) { + _monsterCurBlock = block; + if (testWallFlag(block, -1, wallFlag)) + return 1; + _monsterCurBlock = 0; + } + + if (!(testFlag & 2)) + return 0; + + uint16 obj = _levelBlockProperties[block].assignedObjects; + while (obj & 0x8000) { + LoLMonster *monster = &_monsters[obj & 0x7FFF]; + + if (monster->mode < 13) { + int r = checkDrawObjectSpace(x, y, monster->x, monster->y); + if ((objectWidth + monster->properties->maxWidth) > r) + return 2; + } + + obj = findObject(obj)->nextAssignedObject; + } + + return 0; +} + +int LoLEngine::calcMonsterSkillLevel(int id, int a) { + const uint16 *c = getCharacterOrMonsterStats(id); + int r = (a << 8) / c[4]; + + if (id & 0x8000) { + r = (r * _monsterModifiers2[3 + _monsterDifficulty]) >> 8; + } else { + if (_characters[id].skillLevels[1] > 7) + r = (r - (r >> 1)); + else if (_characters[id].skillLevels[1] > 3 && _characters[id].skillLevels[1] <= 7) + r = (r - (r >> 2)); + } + + return r; +} + +int LoLEngine::checkBlockOccupiedByParty(int x, int y, int testFlag) { + if ((testFlag & 4) && (_currentBlock == calcBlockIndex(x, y))) + return 1; + + return 0; +} + +void LoLEngine::drawBlockObjects(int blockArrayIndex) { + LevelBlockProperty *l = _visibleBlocks[blockArrayIndex]; + uint16 s = l->assignedObjects; + + if (l->direction != _currentDirection) { + l->drawObjects = 0; + l->direction = _currentDirection; + + while (s) { + reassignDrawObjects(_currentDirection, s, l, true); + s = findObject(s)->nextAssignedObject; + } + } + + s = l->drawObjects; + while (s) { + if (s & 0x8000) { + s &= 0x7FFF; + if (blockArrayIndex < 15) + drawMonster(s); + s = _monsters[s].nextDrawObject; + } else { + LoLItem *i = &_itemsInPlay[s]; + int fx = _sceneItemOffs[s & 7] << 1; + int fy = _sceneItemOffs[(s >> 1) & 7] + 5; + + if (i->flyingHeight >= 2 && blockArrayIndex >= 15) { + s = i->nextDrawObject; + continue; + } + + uint8 *shp = 0; + uint16 flg = 0; + + if (i->flyingHeight >= 2) + fy -= ((i->flyingHeight - 1) * 6); + + if ((_itemProperties[i->itemPropertyIndex].flags & 0x1000) && !(i->shpCurFrame_flg & 0xC000)) { + int shpIndex = _itemProperties[i->itemPropertyIndex].flags & 0x800 ? 7 : _itemProperties[i->itemPropertyIndex].shpIndex; + int ii = 0; + for (; ii < 8; ii++) { + if (!_flyingObjects[ii].enable) + continue; + + if (_flyingObjects[ii].item == s) + break; + } + + if (_flyingItemShapes[shpIndex].flipFlags && ((i->x ^ i->y) & 0x20)) + flg |= 0x20; + + flg |= _flyingItemShapes[shpIndex].drawFlags; + + if (ii != 8) { + switch (_currentDirection - (_flyingObjects[ii].direction >> 1) + 3) { + case 1: + case 5: + shpIndex = _flyingItemShapes[shpIndex].shapeFront; + break; + case 3: + shpIndex = _flyingItemShapes[shpIndex].shapeBack; + break; + case 2: + case 6: + flg |= 0x10; + // fall through + case 0: + case 4: + shpIndex = _flyingItemShapes[shpIndex].shapeLeft; + break; + default: + break; + } + + shp = _thrownShapes[shpIndex]; + } + + if (shp) + fy += (shp[2] >> 2); + + } else { + shp = (_itemProperties[i->itemPropertyIndex].flags & 0x40) ? _gameShapes[_itemProperties[i->itemPropertyIndex].shpIndex] : + _itemShapes[_gameShapeMap[_itemProperties[i->itemPropertyIndex].shpIndex << 1]]; + } + + if (shp) + drawItemOrMonster(shp, 0, i->x, i->y, fx, fy, flg, -1, false); + s = i->nextDrawObject; + } + } +} + +void LoLEngine::drawMonster(uint16 id) { + LoLMonster *m = &_monsters[id]; + int16 flg = _monsterDirFlags[(_currentDirection << 2) + m->facing]; + int curFrm = getMonsterCurFrame(m, flg & 0xFFEF); + uint8 *shp = 0; + + if (curFrm == -1) { + shp = _monsterShapes[m->properties->shapeIndex << 4]; + calcDrawingLayerParameters(m->x + _monsterShiftOffs[m->shiftStep << 1], m->y + _monsterShiftOffs[(m->shiftStep << 1) + 1], _shpDmX, _shpDmY, _dmScaleW, _dmScaleH, shp, 0); + } else { + int d = m->flags & 7; + bool flip = m->properties->flags & 0x200 ? true : false; + flg &= 0x10; + shp = _monsterShapes[(m->properties->shapeIndex << 4) + curFrm]; + + if (m->properties->flags & 0x800) + flg |= 0x20; + + uint8 *monsterPalette = d ? _monsterPalettes[(m->properties->shapeIndex << 4) + (curFrm & 0x0F)] + (shp[10] * (d - 1)) : 0; + uint8 *brightnessOverlay = drawItemOrMonster(shp, monsterPalette, m->x + _monsterShiftOffs[m->shiftStep << 1], m->y + _monsterShiftOffs[(m->shiftStep << 1) + 1], 0, 0, flg | 1, -1, flip); + + for (int i = 0; i < 4; i++) { + int v = m->equipmentShapes[i] - 1; + if (v == -1) + break; + + uint8 *shp2 = _monsterDecorationShapes[m->properties->shapeIndex * 192 + v * 48 + curFrm * 3]; + if (!shp2) + continue; + + drawDoorOrMonsterEquipment(shp2, 0, _shpDmX, _shpDmY, flg | 1, brightnessOverlay); + } + } + + if (!m->damageReceived) + return; + + int dW = _screen->getShapeScaledWidth(shp, _dmScaleW) >> 1; + int dH = _screen->getShapeScaledHeight(shp, _dmScaleH) >> 1; + + int bloodAmount = (m->mode == 13) ? (m->fightCurTick << 1) : (m->properties->hitPoints / (m->damageReceived & 0x7FFF)); + + shp = _gameShapes[6]; + + int bloodType = m->properties->flags & 0xC000; + if (bloodType == 0x4000) + bloodType = _flags.use16ColorMode ? 0xBB : 63; + else if (bloodType == 0x8000) + bloodType = _flags.use16ColorMode ? 0x55 : 15; + else if (bloodType == 0xC000) + bloodType = _flags.use16ColorMode ? 0x33 : 74; + else + bloodType = 0; + + uint8 *tbl = new uint8[256]; + if (bloodType) { + for (int i = 0; i < 256; i++) { + tbl[i] = i; + if (i < 2 || i > 7) + continue; + tbl[i] += bloodType; + } + } + + dW += m->hitOffsX; + dH += m->hitOffsY; + + bloodAmount = CLIP(bloodAmount, 1, 4); + + int sW = _dmScaleW / bloodAmount; + int sH = _dmScaleH / bloodAmount; + + _screen->drawShape(_sceneDrawPage1, shp, _shpDmX + dW, _shpDmY + dH, 13, 0x124, tbl, bloodType ? 1 : 0, sW, sH); + + delete[] tbl; +} + +int LoLEngine::getMonsterCurFrame(LoLMonster *m, uint16 dirFlags) { + int tmp = 0; + switch (_monsterAnimType[m->properties->shapeIndex]) { + case 0: + // default + if (dirFlags) { + return (m->mode == 13) ? -1 : (dirFlags + m->currentSubFrame); + } else { + if (m->damageReceived) + return 12; + + switch (m->mode - 5) { + case 0: + return (m->properties->flags & 4) ? 13 : 0; + case 3: + return (m->fightCurTick + 13); + case 6: + return 14; + case 8: + return -1; + default: + return m->currentSubFrame; + } + } + break; + case 1: + // monsters whose outward appearance reflects the damage they have taken + tmp = m->properties->hitPoints; + if (_flags.isTalkie) + tmp = (tmp * _monsterModifiers1[_monsterDifficulty]) >> 8; + if (m->hitPoints > (tmp >> 1)) + tmp = 0; + else if (m->hitPoints > (tmp >> 2)) + tmp = 4; + else + tmp = 8; + + switch (m->mode) { + case 8: + return (m->fightCurTick + tmp); + case 11: + return 12; + case 13: + return (m->fightCurTick + 12); + default: + return tmp; + } + + break; + case 2: + return (m->fightCurTick >= 13) ? 13 : m->fightCurTick; + case 3: + switch (m->mode) { + case 5: + return m->damageReceived ? 5 : 6; + case 8: + return (m->fightCurTick + 6); + case 11: + return 5; + default: + return m->damageReceived ? 5 : m->currentSubFrame; + } + + break; + default: + break; + } + + return 0; +} + +void LoLEngine::reassignDrawObjects(uint16 direction, uint16 itemIndex, LevelBlockProperty *l, bool flag) { + if (l->direction != direction) { + l->direction = 5; + return; + } + + LoLObject *newObject = findObject(itemIndex); + int r = calcObjectPosition(newObject, direction); + uint16 *b = &l->drawObjects; + LoLObject *lastObject = 0; + + while (*b) { + lastObject = findObject(*b); + + if (flag) { + if (calcObjectPosition(lastObject, direction) >= r) + break; + } else { + if (calcObjectPosition(lastObject, direction) > r) + break; + } + + b = &lastObject->nextDrawObject; + } + + newObject->nextDrawObject = *b; + *b = itemIndex; +} + +void LoLEngine::redrawSceneItem() { + assignVisibleBlocks(_currentBlock, _currentDirection); + _screen->fillRect(112, 0, 287, 119, 0); + + static const uint8 sceneClickTileIndex[] = { 13, 16}; + + int16 x1 = 0; + int16 x2 = 0; + + for (int i = 0; i < 2; i++) { + uint8 tile = sceneClickTileIndex[i]; + setLevelShapesDim(tile, x1, x2, 13); + uint16 s = _visibleBlocks[tile]->drawObjects; + + int t = (i << 7) + 1; + while (s) { + if (s & 0x8000) { + s = _monsters[s & 0x7FFF].nextDrawObject; + } else { + LoLItem *item = &_itemsInPlay[s]; + + if (item->shpCurFrame_flg & 0x4000) { + if (checkDrawObjectSpace(item->x, item->y, _partyPosX, _partyPosY) < 320) { + int fx = _sceneItemOffs[s & 7] << 1; + int fy = _sceneItemOffs[(s >> 1) & 7] + 5; + if (item->flyingHeight > 1) + fy -= ((item->flyingHeight - 1) * 6); + + uint8 *shp = (_itemProperties[item->itemPropertyIndex].flags & 0x40) ? _gameShapes[_itemProperties[item->itemPropertyIndex].shpIndex] : + _itemShapes[_gameShapeMap[_itemProperties[item->itemPropertyIndex].shpIndex << 1]]; + + drawItemOrMonster(shp, 0, item->x, item->y, fx, fy, 0, t, 0); + _screen->updateScreen(); + } + } + + s = item->nextDrawObject; + t++; + } + } + } +} + +void LoLEngine::calcSpriteRelPosition(uint16 x1, uint16 y1, int &x2, int &y2, uint16 direction) { + int a = x2 - x1; + int b = y1 - y2; + + if (direction) { + if (direction != 2) + SWAP(a, b); + if (direction != 3) { + a = -a; + if (direction != 1) + b = -b; + } else { + b = -b; + } + } + + x2 = a; + y2 = b; +} + +void LoLEngine::drawDoor(uint8 *shape, uint8 *doorPalette, int index, int unk2, int w, int h, int flags) { + if (!shape) + return; + + uint8 c = _dscDoorY2[(_currentDirection << 5) + unk2]; + int r = (c / 5) + 5 * _dscDimMap[index]; + uint16 d = _dscShapeOvlIndex[r]; + uint16 t = (index << 5) + c; + + _shpDmY = _dscDoorMonsterY[t] + 120; + + if (flags & 1) { + // TODO / UNUSED + flags |= 1; + } + + int u = 0; + + if (flags & 2) { + uint8 dimW = _dscDimMap[index]; + _dmScaleW = _dscDoorMonsterScaleTable[dimW << 1]; + _dmScaleH = _dscDoorMonsterScaleTable[(dimW << 1) + 1]; + u = _dscDoor4[dimW]; + } + + d += 2; + + if (!_dmScaleW || !_dmScaleH) + return; + + int s = _screen->getShapeScaledHeight(shape, _dmScaleH) >> 1; + + if (w) + w = (w * _dmScaleW) >> 8; + + if (h) + h = (h * _dmScaleH) >> 8; + + _shpDmX = _dscDoorMonsterX[t] + w + 200; + _shpDmY = _shpDmY + 4 - s + h - u; + + if (d > 7) + d = 7; + + if (_flags.use16ColorMode) { + uint8 bb = _blockBrightness >> 4; + if (d > bb) + d -= bb; + else + d = 0; + } + + uint8 *brightnessOverlay = _screen->getLevelOverlay(d); + int doorScaledWitdh = _screen->getShapeScaledWidth(shape, _dmScaleW); + + _shpDmX -= (doorScaledWitdh >> 1); + _shpDmY -= s; + + drawDoorOrMonsterEquipment(shape, doorPalette, _shpDmX, _shpDmY, flags, brightnessOverlay); +} + +void LoLEngine::drawDoorOrMonsterEquipment(uint8 *shape, uint8 *objectPalette, int x, int y, int flags, const uint8 *brightnessOverlay) { + int flg = 0; + + if (flags & 0x10) + flg |= 1; + + if (flags & 0x20) + flg |= 0x1000; + + if (flags & 0x40) + flg |= 2; + + if (flg & 0x1000) { + if (objectPalette) + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x9104, objectPalette, brightnessOverlay, 1, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH); + else + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x1104, brightnessOverlay, 1, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH); + } else { + if (objectPalette) + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x8104, objectPalette, brightnessOverlay, 1, _dmScaleW, _dmScaleH); + else + _screen->drawShape(_sceneDrawPage1, shape, x, y, 13, flg | 0x104, brightnessOverlay, 1, _dmScaleW, _dmScaleH); + } +} + +uint8 *LoLEngine::drawItemOrMonster(uint8 *shape, uint8 *monsterPalette, int x, int y, int fineX, int fineY, int flags, int tblValue, bool vflip) { + uint8 *ovl2 = 0; + uint8 *brightnessOverlay = 0; + uint8 tmpOvl[16]; + + if (flags & 0x80) { + flags &= 0xFF7F; + ovl2 = monsterPalette; + monsterPalette = 0; + } else { + ovl2 = _screen->getLevelOverlay(_flags.use16ColorMode ? 5 : 4); + } + + int r = calcDrawingLayerParameters(x, y, _shpDmX, _shpDmY, _dmScaleW, _dmScaleH, shape, vflip); + + if (tblValue == -1) { + r = 7 - ((r / 3) - 1); + r = CLIP(r, 0, 7); + if (_flags.use16ColorMode) { + uint8 bb = _blockBrightness >> 4; + if (r > bb) + r -= bb; + else + r = 0; + } + brightnessOverlay = _screen->getLevelOverlay(r); + } else { + memset(tmpOvl + 1, tblValue, 15); + tmpOvl[0] = 0; + monsterPalette = tmpOvl; + brightnessOverlay = _screen->getLevelOverlay(7); + } + + int flg = flags & 0x10 ? 1 : 0; + if (flags & 0x20) + flg |= 0x1000; + if (flags & 0x40) + flg |= 2; + + if (_flags.use16ColorMode) { + if (_currentLevel != 22) + flg &= 0xDFFF; + + } else { + if (_currentLevel == 22) { + if (brightnessOverlay) + brightnessOverlay[255] = 0; + } else { + flg |= 0x2000; + } + } + + _shpDmX += ((_dmScaleW * fineX) >> 8); + _shpDmY += ((_dmScaleH * fineY) >> 8); + + int dH = _screen->getShapeScaledHeight(shape, _dmScaleH) >> 1; + + if (flg & 0x1000) { + if (monsterPalette) + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x8124, monsterPalette, brightnessOverlay, 0, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH, ovl2); + else + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x124, brightnessOverlay, 0, _transparencyTable1, _transparencyTable2, _dmScaleW, _dmScaleH, ovl2); + } else { + if (monsterPalette) + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x8124, monsterPalette, brightnessOverlay, 1, _dmScaleW, _dmScaleH, ovl2); + else + _screen->drawShape(_sceneDrawPage1, shape, _shpDmX, _shpDmY, 13, flg | 0x124, brightnessOverlay, 1, _dmScaleW, _dmScaleH, ovl2); + } + + _shpDmX -= (_screen->getShapeScaledWidth(shape, _dmScaleW) >> 1); + _shpDmY -= dH; + + return brightnessOverlay; +} + +int LoLEngine::calcDrawingLayerParameters(int x1, int y1, int &x2, int &y2, uint16 &w, uint16 &h, uint8 *shape, int vflip) { + calcSpriteRelPosition(_partyPosX, _partyPosY, x1, y1, _currentDirection); + + if (y1 < 0) { + w = h = x2 = y2 = 0; + return 0; + } + + int l = y1 >> 5; + y2 = _monsterScaleY[l]; + x2 = ((_monsterScaleX[l] * x1) >> 8) + 200; + w = h = (_shpDmY > 120) ? 0x100 : _monsterScaleWH[_shpDmY - 56]; + + if (vflip) + // objects aligned to the ceiling (like the "lobsters" in the mines) + y2 = ((120 - y2) >> 1) + (_screen->getShapeScaledHeight(shape, _dmScaleH) >> 1); + else + y2 -= (_screen->getShapeScaledHeight(shape, _dmScaleH) >> 1); + + return l; +} + +void LoLEngine::updateMonster(LoLMonster *monster) { + static const uint8 flags[] = { 1, 0, 1, 3, 3, 0, 0, 3, 4, 1, 0, 0, 4, 0, 0 }; + if (monster->mode > 14) + return; + + int f = flags[monster->mode]; + if ((monster->speedTick++ < monster->properties->speedTotalWaitTicks) && (!(f & 4))) + return; + + monster->speedTick = 0; + + if (monster->properties->flags & 0x40) { + monster->hitPoints += rollDice(1, 8); + if (monster->hitPoints > monster->properties->hitPoints) + monster->hitPoints = monster->properties->hitPoints; + } + + if (monster->flags & 8) { + monster->destX = _partyPosX; + monster->destY = _partyPosY; + } + + if (f & 2) { + if (updateMonsterAdjustBlocks(monster)) { + setMonsterMode(monster, 7); + f &= 6; + } + } + + if ((f & 1) && (monster->flags & 0x10)) + setMonsterMode(monster, 7); + + if ((monster->mode != 11) && (monster->mode != 14)) { + if (!(_rnd.getRandomNumber(255) & 3)) { + monster->shiftStep = (monster->shiftStep + 1) & 0x0F; + checkSceneUpdateNeed(monster->block); + } + } + + switch (monster->mode) { + case 0: + case 1: + // friendly mode + if (monster->flags & 0x10) { + for (int i = 0; i < 30; i++) { + if (_monsters[i].mode == 1) + setMonsterMode(&_monsters[i], 7); + } + } else if (monster->mode == 1) { + moveMonster(monster); + } + break; + + case 2: + moveMonster(monster); + break; + + case 3: + if (updateMonsterAdjustBlocks(monster)) + setMonsterMode(monster, 7); + for (int i = 0; i < 4; i++) { + if (calcNewBlockPosition(monster->block, i) == _currentBlock) + setMonsterMode(monster, 7); + } + break; + + case 4: + // straying around not tracing the party + moveStrayingMonster(monster); + break; + + case 5: + // second recovery phase after delivering an attack + // monsters will rearrange positions in this phase so as to allow a maximum + // number of monsters possible attacking at the same time + _partyAwake = true; + monster->fightCurTick--; + if ((monster->fightCurTick <= 0) || (checkDrawObjectSpace(_partyPosX, _partyPosY, monster->x, monster->y) > 256) || (monster->flags & 8)) + setMonsterMode(monster, 7); + else + alignMonsterToParty(monster); + break; + + case 6: + // same as mode 5, but without rearranging + if (--monster->fightCurTick <= 0) + setMonsterMode(monster, 7); + break; + + case 7: + // monster destination is set to current party position + // depending on the flag setting this gets updated each round + // monster can't change mode before arriving at destination and/or attacking the party + if (!chasePartyWithDistanceAttacks(monster)) + chasePartyWithCloseAttacks(monster); + checkSceneUpdateNeed(monster->block); + break; + + case 8: + // first recovery phase after delivering an attack + if (++monster->fightCurTick > 2) { + setMonsterMode(monster, 5); + monster->fightCurTick = (int8)((((8 << 8) / monster->properties->fightingStats[4]) * _monsterModifiers3[_monsterDifficulty]) >> 8); + } + checkSceneUpdateNeed(monster->block); + break; + + case 9: + if (--monster->fightCurTick) { + chasePartyWithCloseAttacks(monster); + } else { + setMonsterMode(monster, 7); + monster->flags &= 0xFFF7; + } + break; + + case 12: + checkSceneUpdateNeed(monster->block); + if (++monster->fightCurTick > 13) + runLevelScriptCustom(0x404, -1, monster->id, monster->id, 0, 0); + break; + + case 13: + // monster death + if (++monster->fightCurTick > 2) + killMonster(monster); + checkSceneUpdateNeed(monster->block); + break; + + case 14: + monster->damageReceived = 0; + break; + + default: + break; + } + + if (monster->damageReceived) { + if (monster->damageReceived & 0x8000) + monster->damageReceived &= 0x7FFF; + else + monster->damageReceived = 0; + checkSceneUpdateNeed(monster->block); + } + + monster->flags &= 0xFFEF; +} + +void LoLEngine::moveMonster(LoLMonster *monster) { + static const int8 turnPos[] = { 0, 2, 6, 6, 0, 2, 4, 4, 2, 2, 4, 6, 0, 0, 4, 6, 0 }; + if (monster->x != monster->destX || monster->y != monster->destY) { + walkMonster(monster); + } else if (monster->direction != monster->destDirection) { + int i = (monster->facing << 2) + (monster->destDirection >> 1); + setMonsterDirection(monster, turnPos[i]); + } +} + +void LoLEngine::walkMonster(LoLMonster *monster) { + if (monster->properties->flags & 0x400) + return; + + int s = walkMonsterCalcNextStep(monster); + + if (s == -1) { + if (walkMonsterCheckDest(monster->x, monster->y, monster, 4) != 1) + return; + + _objectLastDirection ^= 4; + setMonsterDirection(monster, _objectLastDirection); + } else { + setMonsterDirection(monster, s); + if (monster->numDistAttacks) { + if (getBlockDistance(monster->block, _currentBlock) >= 2) { + if (checkForPossibleDistanceAttack(monster->block, monster->direction, 3, _currentBlock) != 5) { + if (monster->distAttackTick) + return; + } + } + } + } + + int fx = 0; + int fy = 0; + + getNextStepCoords(monster->x, monster->y, fx, fy, (s == -1) ? _objectLastDirection : s); + placeMonster(monster, fx, fy); +} + +bool LoLEngine::chasePartyWithDistanceAttacks(LoLMonster *monster) { + if (!monster->numDistAttacks) + return false; + + if (monster->distAttackTick > 0) { + monster->distAttackTick--; + return false; + } + + int dir = checkForPossibleDistanceAttack(monster->block, monster->facing, 4, _currentBlock); + if (dir == 5) + return false; + + int s = 0; + + if (monster->flags & 0x10) { + s = monster->properties->numDistWeapons ? rollDice(1, monster->properties->numDistWeapons) : 0; + } else { + s = monster->curDistWeapon++; + if (monster->curDistWeapon >= monster->properties->numDistWeapons) + monster->curDistWeapon = 0; + } + + int flyingObject = monster->properties->distWeapons[s]; + + if (flyingObject & 0xC000) { + if (getBlockDistance(monster->block, _currentBlock) > 1) { + int type = flyingObject & 0x4000 ? 0 : 1; + flyingObject = makeItem(flyingObject & 0x3FFF, 0, 0); + + if (flyingObject) { + if (!launchObject(type, flyingObject, monster->x, monster->y, 12, dir << 1, -1, monster->id | 0x8000, 0x3F)) + deleteItem(flyingObject); + } + } + } else if (!(flyingObject & 0x2000)) { + if (getBlockDistance(monster->block, _currentBlock) > 1) + return false; + + if (flyingObject == 1) { + snd_playSoundEffect(147, -1); + shakeScene(10, 2, 2, 1); + + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1)) + continue; + + int item = removeCharacterItem(i, 15); + if (item) + setItemPosition(item, _partyPosX, _partyPosY, 0, 1); + + inflictDamage(i, 20, 0xFFFF, 0, 2); + } + + } else if (flyingObject == 3) { + // shriek + for (int i = 0; i < 30; i++) { + if (getBlockDistance(monster->block, _monsters[i].block) < 7) + setMonsterMode(monster, 7); + } + _txt->printMessage(2, "%s", getLangString(0x401A)); + + } else if (flyingObject == 4) { + launchMagicViper(); + + } else { + return false; + } + } + + if (monster->numDistAttacks != 255) + monster->numDistAttacks--; + + monster->distAttackTick = (monster->properties->fightingStats[4] * 8) >> 8; + + return true; +} + +void LoLEngine::chasePartyWithCloseAttacks(LoLMonster *monster) { + if (!(monster->flags & 8)) { + int dir = calcMonsterDirection(monster->x & 0xFF00, monster->y & 0xFF00, _partyPosX & 0xFF00, _partyPosY & 0xFF00); + int x1 = _partyPosX; + int y1 = _partyPosY; + + calcSpriteRelPosition(monster->x, monster->y, x1, y1, dir >> 1); + + int t = (x1 < 0) ? -x1 : x1; + if (y1 <= 160 && t <= 80) { + if ((monster->direction == dir) && (monster->facing == (dir >> 1))) { + int dst = getNearestPartyMemberFromPos(monster->x, monster->y); + snd_playSoundEffect(monster->properties->sounds[1], -1); + int m = monster->id | 0x8000; + int hit = battleHitSkillTest(m, dst, 0); + + if (hit) { + int mx = calcInflictableDamage(m, dst, hit); + int dmg = rollDice(2, mx); + inflictDamage(dst, dmg, m, 0, 0); + applyMonsterAttackSkill(monster, dst, dmg); + } + + setMonsterMode(monster, 8); + checkSceneUpdateNeed(monster->block); + + } else { + setMonsterDirection(monster, dir); + checkSceneUpdateNeed(monster->block); + } + return; + } + } + + if (monster->x != monster->destX || monster->y != monster->destY) { + walkMonster(monster); + } else { + setMonsterDirection(monster, monster->destDirection); + setMonsterMode(monster, (rollDice(1, 100) <= 50) ? 4 : 3); + } +} + +int LoLEngine::walkMonsterCalcNextStep(LoLMonster *monster) { + static const int8 walkMonsterTable1[] = { 7, -6, 5, -4, 3, -2, 1, 0 }; + static const int8 walkMonsterTable2[] = { -7, 6, -5, 4, -3, 2, -1, 0 }; + + if (++_monsterStepCounter > 10) { + _monsterStepCounter = 0; + _monsterStepMode ^= 1; + } + + const int8 *tbl = _monsterStepMode ? walkMonsterTable2 : walkMonsterTable1; + + int sx = monster->x; + int sy = monster->y; + int s = monster->direction; + int d = calcMonsterDirection(monster->x, monster->y, monster->destX, monster->destY); + + if (monster->flags & 8) + d ^= 4; + + d = (d - s) & 7; + + if (d >= 5) + s = (s - 1) & 7; + else if (d) + s = (s + 1) & 7; + + for (int i = 7; i > -1; i--) { + s = (s + tbl[i]) & 7; + + int fx = 0; + int fy = 0; + getNextStepCoords(sx, sy, fx, fy, s); + d = walkMonsterCheckDest(fx, fy, monster, 4); + + if (!d) + return s; + + if ((d != 1) || (s & 1) || (!(monster->properties->flags & 0x80))) + continue; + + uint8 w = _levelBlockProperties[_monsterCurBlock].walls[(s >> 1) ^ 2]; + + if (_wllWallFlags[w] & 0x20) { + if (_specialWallTypes[w] == 5) { + openCloseDoor(_monsterCurBlock, 1); + return -1; + } + } + + if (_wllWallFlags[w] & 8) + return -1; + } + + return -1; +} + +int LoLEngine::checkForPossibleDistanceAttack(uint16 monsterBlock, int direction, int distance, uint16 curBlock) { + int mdist = getBlockDistance(curBlock, monsterBlock); + + if (mdist > distance) + return 5; + + int dir = calcMonsterDirection(monsterBlock & 0x1F, monsterBlock >> 5, curBlock & 0x1F, curBlock >> 5); + if ((dir & 1) || (dir != (direction << 1))) + return 5; + + if (((monsterBlock & 0x1F) != (curBlock & 0x1F)) && ((monsterBlock & 0xFFE0) != (curBlock & 0xFFE0))) + return 5; + + if (distance < 0) + return 5; + + int p = monsterBlock; + + for (int i = 0; i < distance; i++) { + p = calcNewBlockPosition(p, direction); + + if (p == curBlock) + return direction; + + if (_wllWallFlags[_levelBlockProperties[p].walls[direction ^ 2]] & 2) + return 5; + + if (_levelBlockProperties[p].assignedObjects & 0x8000) + return 5; + } + + return 5; +} + +int LoLEngine::walkMonsterCheckDest(int x, int y, LoLMonster *monster, int unk) { + uint8 m = monster->mode; + monster->mode = 15; + + int objType = checkBlockBeforeObjectPlacement(x, y, monster->properties->maxWidth, 7, monster->properties->flags & 0x1000 ? 32 : unk); + + monster->mode = m; + return objType; +} + +void LoLEngine::getNextStepCoords(int16 srcX, int16 srcY, int &newX, int &newY, uint16 direction) { + static const int8 stepAdjustX[] = { 0, 32, 32, 32, 0, -32, -32, -32 }; + static const int8 stepAdjustY[] = { -32, -32, 0, 32, 32, 32, 0, -32 }; + + newX = (srcX + stepAdjustX[direction]) & 0x1FFF; + newY = (srcY + stepAdjustY[direction]) & 0x1FFF; +} + +void LoLEngine::alignMonsterToParty(LoLMonster *monster) { + uint8 mdir = monster->direction >> 1; + uint16 mx = monster->x; + uint16 my = monster->y; + uint16 *pos = (mdir & 1) ? &my : &mx; + bool centered = (*pos & 0x7F) == 0; + + bool posFlag = true; + if (monster->properties->maxWidth <= 63) { + if (centered) { + bool r = false; + + if (monster->nextAssignedObject & 0x8000) { + r = true; + } else { + uint16 id = _levelBlockProperties[monster->block].assignedObjects; + id = (id & 0x8000) ? (id & 0x7FFF) : 0xFFFF; + + if (id != monster->id) { + r = true; + } else { + for (int i = 0; i < 3; i++) { + mdir = (mdir + 1) & 3; + id = _levelBlockProperties[calcNewBlockPosition(monster->block, mdir)].assignedObjects; + id = (id & 0x8000) ? (id & 0x7FFF) : 0xFFFF; + if (id != 0xFFFF) { + r = true; + break; + } + } + } + } + + if (r) + posFlag = false; + } else { + posFlag = false; + } + } + + if (centered && posFlag) + return; + + if (posFlag) { + if (*pos & 0x80) + *pos -= 32; + else + *pos += 32; + } else { + if (*pos & 0x80) + *pos += 32; + else + *pos -= 32; + } + + if (walkMonsterCheckDest(mx, my, monster, 4)) + return; + + int fx = _partyPosX; + int fy = _partyPosY; + calcSpriteRelPosition(mx, my, fx, fy, monster->direction >> 1); + + if (fx < 0) + fx = -fx; + + if (fy > 160 || fx > 80) + return; + + placeMonster(monster, mx, my); +} + +void LoLEngine::moveStrayingMonster(LoLMonster *monster) { + int x = 0; + int y = 0; + + if (monster->fightCurTick) { + uint8 d = (monster->direction - monster->fightCurTick) & 6; + uint8 id = monster->id; + + for (int i = 0; i < 7; i++) { + getNextStepCoords(monster->x, monster->y, x, y, d); + + if (!walkMonsterCheckDest(x, y, monster, 4)) { + placeMonster(monster, x, y); + setMonsterDirection(monster, d); + if (!i) { + if (++id > 3) + monster->fightCurTick = 0; + } + return; + } + + d = (d + monster->fightCurTick) & 6; + } + setMonsterMode(monster, 3); + + } else { + monster->direction &= 6; + getNextStepCoords(monster->x, monster->y, x, y, monster->direction); + if (!walkMonsterCheckDest(x, y, monster, 4)) { + placeMonster(monster, x, y); + } else { + monster->fightCurTick = _rnd.getRandomBit() ? 2 : -2; + monster->direction = (monster->direction + monster->fightCurTick) & 6; + } + } +} + +void LoLEngine::killMonster(LoLMonster *monster) { + setMonsterMode(monster, 14); + monsterDropItems(monster); + checkSceneUpdateNeed(monster->block); + + uint8 w = _levelBlockProperties[monster->block].walls[0]; + uint16 f = _levelBlockProperties[monster->block].flags; + if (_wllVmpMap[w] == 0 && _wllShapeMap[w] == 0 && !(f & 0x40) && !(monster->properties->flags & 0x1000)) + _levelBlockProperties[monster->block].flags |= 0x80; + + placeMonster(monster, 0, 0); +} + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/sprites_rpg.cpp b/engines/kyra/engine/sprites_rpg.cpp new file mode 100644 index 0000000000..87c2513c09 --- /dev/null +++ b/engines/kyra/engine/sprites_rpg.cpp @@ -0,0 +1,46 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) + +#include "kyra/engine/kyra_rpg.h" + +namespace Kyra { + +int KyraRpgEngine::getBlockDistance(uint16 block1, uint16 block2) { + int b1x = block1 & 0x1F; + int b1y = block1 >> 5; + int b2x = block2 & 0x1F; + int b2y = block2 >> 5; + + uint8 dy = ABS(b2y - b1y); + uint8 dx = ABS(b2x - b1x); + + if (dx > dy) + SWAP(dx, dy); + + return (dx >> 1) + dy; +} + +} // namespace Kyra + +#endif diff --git a/engines/kyra/engine/timer.cpp b/engines/kyra/engine/timer.cpp new file mode 100644 index 0000000000..9728838015 --- /dev/null +++ b/engines/kyra/engine/timer.cpp @@ -0,0 +1,304 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +namespace { +struct TimerResync : public Common::UnaryFunction { + uint32 _tickLength, _curTime; + TimerResync(KyraEngine_v1 *vm, uint32 curTime) : _tickLength(vm->tickLength()), _curTime(curTime) {} + + void operator()(TimerEntry &entry) const { + if (entry.lastUpdate < 0) { + if ((uint32)(ABS(entry.lastUpdate)) >= entry.countdown * _tickLength) + entry.nextRun = 0; + else + entry.nextRun = _curTime + entry.lastUpdate + entry.countdown * _tickLength; + } else { + uint32 nextRun = entry.lastUpdate + entry.countdown * _tickLength; + if (_curTime < nextRun) + nextRun = 0; + entry.nextRun = nextRun; + } + } +}; + +struct TimerEqual : public Common::UnaryFunction { + uint8 _id; + + TimerEqual(uint8 id) : _id(id) {} + + bool operator()(const TimerEntry &entry) const { + return entry.id == _id; + } +}; +} // end of anonymous namespace + +void TimerManager::pause(bool p) { + if (p) { + ++_isPaused; + + if (_isPaused == 1) { + _isPaused = true; + _pauseStart = _system->getMillis(); + } + } else if (!p && _isPaused > 0) { + --_isPaused; + + if (_isPaused == 0) { + const uint32 pausedTime = _system->getMillis() - _pauseStart; + _nextRun += pausedTime; + + for (Iterator pos = _timers.begin(); pos != _timers.end(); ++pos) { + pos->lastUpdate += pausedTime; + pos->nextRun += pausedTime; + } + } + } +} + +void TimerManager::reset() { + for (Iterator pos = _timers.begin(); pos != _timers.end(); ++pos) + delete pos->func; + + _timers.clear(); +} + +void TimerManager::addTimer(uint8 id, TimerFunc *func, int countdown, bool enabled) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) { + warning("Adding already existing timer %d", id); + return; + } + + TimerEntry newTimer; + + newTimer.id = id; + newTimer.countdown = countdown; + newTimer.enabled = enabled ? 1 : 0; + newTimer.lastUpdate = newTimer.nextRun = 0; + newTimer.func = func; + newTimer.pauseStartTime = 0; + + _timers.push_back(newTimer); +} + +void TimerManager::update() { + if (_system->getMillis() < _nextRun || _isPaused) + return; + + _nextRun += 99999; + + for (Iterator pos = _timers.begin(); pos != _timers.end(); ++pos) { + if (pos->enabled == 1 && pos->countdown >= 0) { + if (pos->nextRun <= _system->getMillis()) { + if (pos->func && pos->func->isValid()) { + (*pos->func)(pos->id); + } + + uint32 curTime = _system->getMillis(); + pos->lastUpdate = curTime; + pos->nextRun = curTime + pos->countdown * _vm->tickLength(); + } + + _nextRun = MIN(_nextRun, pos->nextRun); + } + } +} + +void TimerManager::resync() { + const uint32 curTime = _isPaused ? _pauseStart : _system->getMillis(); + + _nextRun = 0; // force rerun + Common::for_each(_timers.begin(), _timers.end(), TimerResync(_vm, curTime)); +} + +void TimerManager::resetNextRun() { + _nextRun = 0; +} + +void TimerManager::setCountdown(uint8 id, int32 countdown) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) { + timer->countdown = countdown; + + if (countdown >= 0) { + uint32 curTime = _system->getMillis(); + timer->lastUpdate = curTime; + timer->nextRun = curTime + countdown * _vm->tickLength(); + if (timer->enabled & 2) + timer->pauseStartTime = curTime; + + _nextRun = MIN(_nextRun, timer->nextRun); + } + } else { + warning("TimerManager::setCountdown: No timer %d", id); + } +} + +void TimerManager::setDelay(uint8 id, int32 countdown) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + timer->countdown = countdown; + else + warning("TimerManager::setDelay: No timer %d", id); +} + +int32 TimerManager::getDelay(uint8 id) const { + CIterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + return timer->countdown; + + warning("TimerManager::getDelay: No timer %d", id); + return -1; +} + +void TimerManager::setNextRun(uint8 id, uint32 nextRun) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) { + if (timer->enabled & 2) + timer->pauseStartTime = _system->getMillis(); + timer->nextRun = nextRun; + return; + } + + warning("TimerManager::setNextRun: No timer %d", id); +} + +uint32 TimerManager::getNextRun(uint8 id) const { + CIterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + return timer->nextRun; + + warning("TimerManager::getNextRun: No timer %d", id); + return 0xFFFFFFFF; +} + +void TimerManager::pauseSingleTimer(uint8 id, bool p) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + + if (timer == _timers.end()) { + warning("TimerManager::pauseSingleTimer: No timer %d", id); + return; + } + + if (p) { + timer->pauseStartTime = _system->getMillis(); + timer->enabled |= 2; + } else if (timer->pauseStartTime) { + int32 elapsedTime = _system->getMillis() - timer->pauseStartTime; + timer->enabled &= (~2); + timer->lastUpdate += elapsedTime; + timer->nextRun += elapsedTime; + resetNextRun(); + timer->pauseStartTime = 0; + } +} + +bool TimerManager::isEnabled(uint8 id) const { + CIterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + return (timer->enabled & 1); + + warning("TimerManager::isEnabled: No timer %d", id); + return false; +} + +void TimerManager::enable(uint8 id) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + timer->enabled |= 1; + else + warning("TimerManager::enable: No timer %d", id); +} + +void TimerManager::disable(uint8 id) { + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) + timer->enabled &= (~1); + else + warning("TimerManager::disable: No timer %d", id); +} + +void TimerManager::loadDataFromFile(Common::SeekableReadStream &file, int version) { + const uint32 loadTime = _isPaused ? _pauseStart : _system->getMillis(); + + if (version <= 7) { + _nextRun = 0; + for (int i = 0; i < 32; ++i) { + uint8 enabled = file.readByte(); + int32 countdown = file.readSint32BE(); + uint32 nextRun = file.readUint32BE(); + + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(i)); + if (timer != _timers.end()) { + timer->enabled = enabled; + timer->countdown = countdown; + + if (nextRun) { + timer->nextRun = nextRun + loadTime; + timer->lastUpdate = timer->nextRun - countdown * _vm->tickLength(); + } else { + timer->nextRun = loadTime; + timer->lastUpdate = loadTime - countdown * _vm->tickLength(); + } + } else { + warning("Loading timer data for non existing timer %d", i); + } + } + } else { + int entries = file.readByte(); + for (int i = 0; i < entries; ++i) { + uint8 id = file.readByte(); + + Iterator timer = Common::find_if(_timers.begin(), _timers.end(), TimerEqual(id)); + if (timer != _timers.end()) { + timer->enabled = file.readByte(); + timer->countdown = file.readSint32BE(); + timer->lastUpdate = file.readSint32BE(); + } else { + warning("Loading timer data for non existing timer %d", id); + file.seek(7, SEEK_CUR); + } + } + + resync(); + } +} + +void TimerManager::saveDataToFile(Common::WriteStream &file) const { + const uint32 saveTime = _isPaused ? _pauseStart : _system->getMillis(); + + file.writeByte(count()); + for (CIterator pos = _timers.begin(); pos != _timers.end(); ++pos) { + file.writeByte(pos->id); + file.writeByte(pos->enabled); + file.writeSint32BE(pos->countdown); + file.writeSint32BE(pos->lastUpdate - saveTime); + } +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/timer.h b/engines/kyra/engine/timer.h new file mode 100644 index 0000000000..a753707b8a --- /dev/null +++ b/engines/kyra/engine/timer.h @@ -0,0 +1,106 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_TIMER_H +#define KYRA_TIMER_H + +#include "kyra/kyra_v1.h" + +#include "common/list.h" +#include "common/stream.h" +#include "common/func.h" + +namespace Kyra { + +typedef Common::Functor1 TimerFunc; + +struct TimerEntry { + uint8 id; + int32 countdown; + int8 enabled; + + int32 lastUpdate; + uint32 nextRun; + + TimerFunc *func; + + uint32 pauseStartTime; +}; + +class TimerManager { +public: + TimerManager(KyraEngine_v1 *vm, OSystem *sys) : _vm(vm), _system(sys), _timers(), _nextRun(0), _isPaused(0), _pauseStart(0) {} + ~TimerManager() { reset(); } + + void pause(bool p); + + void reset(); + + void addTimer(uint8 id, TimerFunc *func, int countdown, bool enabled); + + int count() const { return _timers.size(); } + + void update(); + + void resetNextRun(); + + void setCountdown(uint8 id, int32 countdown); + void setDelay(uint8 id, int32 countdown); + int32 getDelay(uint8 id) const; + void setNextRun(uint8 id, uint32 nextRun); + uint32 getNextRun(uint8 id) const; + + void pauseSingleTimer(uint8 id, bool p); + + bool isEnabled(uint8 id) const; + void enable(uint8 id); + void disable(uint8 id); + + void loadDataFromFile(Common::SeekableReadStream &file, int version); + void saveDataToFile(Common::WriteStream &file) const; + +private: + void resync(); + + KyraEngine_v1 *_vm; + OSystem *_system; + Common::List _timers; + uint32 _nextRun; + + uint _isPaused; + uint32 _pauseStart; + + typedef Common::List::iterator Iterator; + typedef Common::List::const_iterator CIterator; +}; + +class PauseTimer { +public: + PauseTimer(TimerManager &timer) : _timer(timer) { _timer.pause(true); } + ~PauseTimer() { _timer.pause(false); } +private: + TimerManager &_timer; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/engine/timer_eob.cpp b/engines/kyra/engine/timer_eob.cpp new file mode 100644 index 0000000000..8cac8d8abc --- /dev/null +++ b/engines/kyra/engine/timer_eob.cpp @@ -0,0 +1,361 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/eobcommon.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +#ifdef ENABLE_EOB + +namespace Kyra { + +#define TimerV2(x) new Common::Functor1Mem(this, &EoBCoreEngine::x) + +void EoBCoreEngine::setupTimers() { + _timer->addTimer(0, TimerV2(timerProcessCharacterExchange), 9, false); + _timer->addTimer(1, TimerV2(timerProcessFlyingObjects), 3, true); + _timer->addTimer(0x20, TimerV2(timerProcessMonsters), 20, true); + _timer->addTimer(0x21, TimerV2(timerProcessMonsters), 20, true); + _timer->addTimer(0x22, TimerV2(timerProcessMonsters), 20, true); + _timer->addTimer(0x23, TimerV2(timerProcessMonsters), 20, true); + _timer->setNextRun(0x20, _system->getMillis()); + _timer->setNextRun(0x21, _system->getMillis() + 7 * _tickLength); + _timer->setNextRun(0x22, _system->getMillis() + 14 * _tickLength); + _timer->setNextRun(0x23, _system->getMillis() + 14 * _tickLength); + _timer->addTimer(0x30, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(0x31, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(0x32, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(0x33, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(0x34, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(0x35, TimerV2(timerSpecialCharacterUpdate), 50, false); + _timer->addTimer(4, TimerV2(timerProcessDoors), 5, true); + _timer->addTimer(5, TimerV2(timerUpdateTeleporters), 10, true); + _timer->addTimer(6, TimerV2(timerUpdateFoodStatus), 1080, true); + _timer->addTimer(7, TimerV2(timerUpdateMonsterIdleAnim), 25, true); + _timer->resetNextRun(); +} + +void EoBCoreEngine::setCharEventTimer(int charIndex, uint32 countdown, int evnt, int updateExistingTimer) { + uint32 ntime = _system->getMillis() + countdown * _tickLength; + uint8 timerId = 0x30 | (charIndex & 0x0F); + EoBCharacter *c = &_characters[charIndex]; + + if (!_timer->isEnabled(timerId)) { + c->timers[0] = ntime; + c->events[0] = evnt; + _timer->setCountdown(timerId, countdown); + enableTimer(timerId); + return; + } + + if (ntime < _timer->getNextRun(timerId)) + _timer->setNextRun(timerId, ntime); + _timer->resetNextRun(); + + if (updateExistingTimer) { + bool updated = false; + int d = -1; + + for (int i = 0; i < 10 && updated == false; i++) { + if (d == -1 && !c->timers[i]) + d = i; + + if (!updated && c->events[i] == evnt) { + d = i; + updated = true; + } + } + + assert(d != -1); + + c->timers[d] = ntime; + c->events[d] = evnt; + } else { + for (int i = 0; i < 10; i++) { + if (c->timers[i]) + continue; + c->timers[i] = ntime; + c->events[i] = evnt; + return; + } + } +} + +void EoBCoreEngine::deleteCharEventTimer(int charIndex, int evnt) { + EoBCharacter *c = &_characters[charIndex]; + for (int i = 0; i < 10; i++) { + if (c->events[i] == evnt) { + c->events[i] = 0; + c->timers[i] = 0; + } + } + setupCharacterTimers(); +} + +void EoBCoreEngine::setupCharacterTimers() { + for (int i = 0; i < 6; i++) { + EoBCharacter *c = &_characters[i]; + if (!testCharacter(i, 1)) + continue; + + uint32 nextTimer = 0xFFFFFFFF; + + for (int ii = 0; ii < 10; ii++) { + if (c->timers[ii] && c->timers[ii] < nextTimer) + nextTimer = c->timers[ii]; + } + uint32 ctime = _system->getMillis(); + + if (nextTimer == 0xFFFFFFFF) + _timer->disable(0x30 | i); + else { + enableTimer(0x30 | i); + _timer->setCountdown(0x30 | i, (nextTimer - ctime) / _tickLength); + } + } + _timer->resetNextRun(); +} + +void EoBCoreEngine::advanceTimers(uint32 millis) { + uint32 ct = _system->getMillis(); + for (int i = 0; i < 6; i++) { + EoBCharacter *c = &_characters[i]; + for (int ii = 0; ii < 10; ii++) { + if (c->timers[ii] > ct) { + uint32 chrt = c->timers[ii] - ct; + c->timers[ii] = chrt > millis ? ct + chrt - millis : ct; + } + } + } + + setupCharacterTimers(); + + if (_scriptTimersMode & 1) { + for (int i = 0; i < _scriptTimersCount; i++) { + if (_scriptTimers[i].next > ct) { + uint32 chrt = _scriptTimers[i].next - ct; + _scriptTimers[i].next = chrt > millis ? ct + chrt - millis : ct; + } + } + } + + for (int i = 0; i < 5; i++) { + if (_wallsOfForce[i].duration > ct) { + uint32 chrt = _wallsOfForce[i].duration - ct; + _wallsOfForce[i].duration = chrt > millis ? ct + chrt - millis : ct; + } + } +} + +void EoBCoreEngine::timerProcessCharacterExchange(int timerNum) { + _charExchangeSwap ^= 1; + if (_charExchangeSwap) { + int index = _exchangeCharacterId; + _exchangeCharacterId = -1; + gui_drawCharPortraitWithStats(index); + _exchangeCharacterId = index; + } else { + gui_drawCharPortraitWithStats(_exchangeCharacterId); + } +} + +void EoBCoreEngine::timerProcessFlyingObjects(int timerNum) { + static const uint8 dirPosIndex[] = { 0x82, 0x83, 0x00, 0x01, 0x01, 0x80, 0x03, 0x82, 0x02, 0x03, 0x80, 0x81, 0x81, 0x00, 0x83, 0x02 }; + for (int i = 0; i < 10; i++) { + EoBFlyingObject *fo = &_flyingObjects[i]; + if (!fo->enable) + continue; + + bool endFlight = fo->distance == 0; + + uint8 pos = dirPosIndex[(fo->direction << 2) + (fo->curPos & 3)]; + uint16 bl = fo->curBlock; + bool newBl = (pos & 0x80) ? true : false; + + if (newBl) { + bl = calcNewBlockPosition(fo->curBlock, fo->direction); + pos &= 3; + fo->starting = 0; + } + + if (updateObjectFlight(fo, bl, pos)) { + if (newBl) + runLevelScript(bl, 0x10); + if (updateFlyingObjectHitTest(fo, bl, pos)) + endFlight = true; + } else { + if (fo->flags & 0x20) { + if (!updateFlyingObjectHitTest(fo, fo->curBlock, fo->curPos)) + explodeObject(fo, fo->curBlock, fo->item); + } + endFlight = true; + } + + if (endFlight) + endObjectFlight(fo); + + _sceneUpdateRequired = true; + } +} + +void EoBCoreEngine::timerProcessMonsters(int timerNum) { + updateMonsters(timerNum & 0x0F); +} + +void EoBCoreEngine::timerSpecialCharacterUpdate(int timerNum) { + int charIndex = timerNum & 0x0F; + EoBCharacter *c = &_characters[charIndex]; + uint32 ctime = _system->getMillis(); + + for (int i = 0; i < 10; i++) { + if (!c->timers[i]) + continue; + if (c->timers[i] > ctime) + continue; + + c->timers[i] = 0; + int evt = c->events[i]; + + if (evt < 0) { + removeCharacterEffect(-evt, charIndex, 1); + continue; + } + + int od = _screen->curDimIndex(); + Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); + _screen->setScreenDim(7); + + switch (evt) { + case 2: + case 3: + setCharEventTimer(charIndex, (c->effectFlags & 0x10000) ? 9 : 36, evt + 2, 1); + // fall through + case 0: + case 1: + case 4: + case 5: + setWeaponSlotStatus(charIndex, evt / 2, evt & 1); + break; + + case 6: + c->damageTaken = 0; + gui_drawCharPortraitWithStats(charIndex); + break; + + case 7: + _txt->printMessage(_characterStatusStrings7[0], -1, c->name); + c->strengthCur = c->strengthMax; + c->strengthExtCur = c->strengthExtMax; + if (_currentControlMode == 2) + gui_drawCharPortraitWithStats(charIndex); + break; + + case 8: + if (c->flags & 2) { + calcAndInflictCharacterDamage(charIndex, 0, 0, 5, 0x400, 5, 3); + setCharEventTimer(charIndex, 546, 8, 1); + } else { + c->flags &= ~2; + gui_drawCharPortraitWithStats(charIndex); + } + break; + + case 9: + if (c->flags & 4) { + _txt->printMessage(_characterStatusStrings9[0], -1, c->name); + c->flags &= ~4; + gui_drawCharPortraitWithStats(charIndex); + } + break; + + case 11: + if (c->disabledSlots & 4) { + c->disabledSlots &= ~4; + if (_openBookChar == charIndex && _updateFlags) + gui_drawSpellbook(); + } + break; + + case 12: + c->effectFlags &= ~0x1000; + if (_characterStatusStrings12) + _txt->printMessage(_characterStatusStrings12[0], -1, c->name); + break; + + default: + break; + } + + _screen->setScreenDim(od); + _screen->setFont(of); + } + + uint32 nextTimer = 0xFFFFFFFF; + for (int i = 0; i < 10; i++) { + if (c->timers[i] && c->timers[i] < nextTimer) + nextTimer = c->timers[i]; + } + + if (nextTimer == 0xFFFFFFFF) + _timer->disable(timerNum); + else + _timer->setCountdown(timerNum, (nextTimer - ctime) / _tickLength); +} + +void EoBCoreEngine::timerUpdateTeleporters(int timerNum) { + _teleporterPulse ^= 1; + for (int i = 0; i < 18; i++) { + uint8 w = _visibleBlocks[i]->walls[_sceneDrawVarDown]; + if ((w == _teleporterWallId) || (_flags.gameID == GI_EOB2 && w == 74)) { + _sceneUpdateRequired = true; + return; + } + } +} + +void EoBCoreEngine::timerUpdateFoodStatus(int timerNum) { + for (int i = 0; i < 6; i++) { + // Ring of Sustenance check + if (checkInventoryForRings(i, 2)) + continue; + EoBCharacter *c = &_characters[i]; + if (c->food != 0 && c->flags & 1 && c->hitPointsCur > -10) { + c->food--; + gui_drawFoodStatusGraph(i); + } + } +} + +void EoBCoreEngine::timerUpdateMonsterIdleAnim(int timerNum) { + for (int i = 0; i < 30; i++) { + EoBMonsterInPlay *m = &_monsters[i]; + if (m->mode == 7 || m->mode == 10 || (m->flags & 0x20) || (rollDice(1, 2, 0) != 1)) + continue; + m->idleAnimState = (rollDice(1, 2, 0) << 4) | rollDice(1, 2, 0); + checkSceneUpdateNeed(m->block); + } +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/engine/timer_hof.cpp b/engines/kyra/engine/timer_hof.cpp new file mode 100644 index 0000000000..1973e2e593 --- /dev/null +++ b/engines/kyra/engine/timer_hof.cpp @@ -0,0 +1,110 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_hof.h" +#include "kyra/engine/timer.h" + +namespace Kyra { + +#define TimerV2(x) new Common::Functor1Mem(this, &KyraEngine_HoF::x) + +void KyraEngine_HoF::setupTimers() { + _timer->addTimer(0, 0, 5, 1); + _timer->addTimer(1, TimerV2(timerFadeOutMessage), -1, 1); + _timer->addTimer(2, TimerV2(timerCauldronAnimation), 1, 1); + _timer->addTimer(3, TimerV2(timerFunc4), 1, 0); + _timer->addTimer(4, TimerV2(timerFunc5), 1, 0); + _timer->addTimer(5, TimerV2(timerBurnZanthia), 1, 0); +} + +void KyraEngine_HoF::timerFadeOutMessage(int arg) { + if (_shownMessage) + _fadeMessagePalette = 1; +} + +void KyraEngine_HoF::timerCauldronAnimation(int arg) { + int animation = -1; + + // HACK: We don't allow inventory animations while the inventory is backed off, which means not shown usually. + // This prevents for example that the cauldron animation is shown in the meanwhile scene with Marco and the Hand in Chapter 2. + if (_inventorySaved) + return; + + if (queryGameFlag(2) && _mainCharacter.sceneId != 34 && _mainCharacter.sceneId != 73 && !_invWsa.wsa && !_invWsa.running) { + if (animation == -1) + animation = _rnd.getRandomNumberRng(1, 6); + + char filename[13]; + strcpy(filename, "CAULD00.WSA"); + filename[5] = (animation / 10) + '0'; + filename[6] = (animation % 10) + '0'; + loadInvWsa(filename, 0, 8, 0, -1, -1, 1); + } +} + +void KyraEngine_HoF::timerFunc4(int arg) { + _timer->disable(3); + setGameFlag(0xD8); +} + +void KyraEngine_HoF::timerFunc5(int arg) { + _timer->disable(4); + _screen->hideMouse(); + _specialSceneScriptState[5] = 1; + for (int i = 68; i <= 75; ++i) { + updateSceneAnim(4, i); + delay(6); + } + _deathHandler = 4; +} + +void KyraEngine_HoF::timerBurnZanthia(int arg) { + _timer->disable(5); + _screen->hideMouse(); + snd_playSoundEffect(0x2D); + runAnimationScript("_ZANBURN.EMC", 0, 1, 1, 0); + _deathHandler = 7; + snd_playWanderScoreViaMap(0x53, 1); +} + +void KyraEngine_HoF::setTimer1DelaySecs(int secs) { + if (secs == -1) + secs = 32000; + + _timer->setCountdown(1, secs * 60); +} + +void KyraEngine_HoF::setWalkspeed(uint8 newSpeed) { + if (!_timer) + return; + + if (newSpeed < 5) + newSpeed = 3; + else + newSpeed = 5; + + _configWalkspeed = newSpeed; + _timer->setDelay(0, newSpeed); +} + + +} // End of namespace Kyra diff --git a/engines/kyra/engine/timer_lok.cpp b/engines/kyra/engine/timer_lok.cpp new file mode 100644 index 0000000000..47f8d0c80b --- /dev/null +++ b/engines/kyra/engine/timer_lok.cpp @@ -0,0 +1,192 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_lok.h" +#include "kyra/graphics/animator_lok.h" +#include "kyra/engine/timer.h" + +namespace Kyra { + +#define TimerV1(x) new Common::Functor1Mem(this, &KyraEngine_LoK::x) + +void KyraEngine_LoK::setupTimers() { + for (int i = 0; i <= 4; ++i) + _timer->addTimer(i, 0, -1, 1); + + _timer->addTimer(5, 0, 5, 1); + _timer->addTimer(6, 0, 7, 1); + _timer->addTimer(7, 0, 8, 1); + _timer->addTimer(8, 0, 9, 1); + _timer->addTimer(9, 0, 7, 1); + + for (int i = 10; i <= 13; ++i) + _timer->addTimer(i, 0, 420, 1); + + _timer->addTimer(14, TimerV1(timerAsWillowispTimeout), 600, 1); + _timer->addTimer(15, TimerV1(timerUpdateHeadAnims), 11, 1); + _timer->addTimer(16, TimerV1(timerTulipCreator), 7200, 1); + _timer->addTimer(17, TimerV1(timerRubyCreator), 7200, 1); + _timer->addTimer(18, TimerV1(timerAsInvisibleTimeout), 600, 1); + _timer->addTimer(19, TimerV1(timerRedrawAmulet), 600, 1); + + _timer->addTimer(20, 0, 7200, 1); + _timer->addTimer(21, TimerV1(timerLavenderRoseCreator), 18000, 1); + _timer->addTimer(22, 0, 7200, 1); + + _timer->addTimer(23, 0, 10800, 1); + _timer->addTimer(24, TimerV1(timerAcornCreator), 10800, 1); + _timer->addTimer(25, 0, 10800, 1); + _timer->addTimer(26, TimerV1(timerBlueberryCreator), 10800, 1); + _timer->addTimer(27, 0, 10800, 1); + + _timer->addTimer(28, 0, 21600, 1); + _timer->addTimer(29, 0, 7200, 1); + _timer->addTimer(30, 0, 10800, 1); + + _timer->addTimer(31, TimerV1(timerFadeText), -1, 1); + _timer->addTimer(32, TimerV1(timerWillowispFrameTimer), 9, 1); + _timer->addTimer(33, TimerV1(timerInvisibleFrameTimer), 3, 1); +} + +void KyraEngine_LoK::timerUpdateHeadAnims(int timerNum) { + static const int8 frameTable[] = { + 4, 5, 4, 5, 4, 5, 0, 1, + 4, 5, 4, 4, 6, 4, 8, 1, + 9, 4, -1 + }; + + if (_talkingCharNum < 0) + return; + + _currHeadShape = frameTable[_currentHeadFrameTableIndex]; + ++_currentHeadFrameTableIndex; + + if (frameTable[_currentHeadFrameTableIndex] == -1) + _currentHeadFrameTableIndex = 0; + + _animator->animRefreshNPC(0); + _animator->animRefreshNPC(_talkingCharNum); +} + +void KyraEngine_LoK::timerTulipCreator(int timerNum) { + if (_currentCharacter->sceneId == 0x1C) + return; + + setItemCreationFlags(17, 3); +} + +void KyraEngine_LoK::timerRubyCreator(int timerNum) { + if (_currentCharacter->sceneId == 0x23) + return; + + setItemCreationFlags(22, 4); +} + +void KyraEngine_LoK::timerLavenderRoseCreator(int timerNum) { + if (_currentCharacter->sceneId == 0x06) + return; + + setItemCreationFlags(0, 4); +} + +void KyraEngine_LoK::timerAcornCreator(int timerNum) { + if (_currentCharacter->sceneId == 0x1F) + return; + + setItemCreationFlags(72, 5); +} + +void KyraEngine_LoK::timerBlueberryCreator(int timerNum) { + if (_currentCharacter->sceneId == 0x28) + return; + + setItemCreationFlags(26, 7); +} + +void KyraEngine_LoK::setItemCreationFlags(int offset, int count) { + int rndNr = _rnd.getRandomNumberRng(0, count); + + for (int i = 0; i <= count; i++) { + if (!queryGameFlag(rndNr + offset)) { + setGameFlag(rndNr + offset); + break; + } else { + rndNr++; + if (rndNr > count) + rndNr = 0; + } + } +} + +void KyraEngine_LoK::timerFadeText(int timerNum) { + _fadeText = true; +} + +void KyraEngine_LoK::timerWillowispFrameTimer(int timerNum) { + if (_brandonStatusBit & 2) + _brandonStatusBit0x02Flag = 1; +} + +void KyraEngine_LoK::timerInvisibleFrameTimer(int timerNum) { + if (_brandonStatusBit & 0x20) + _brandonStatusBit0x20Flag = 1; +} + +void KyraEngine_LoK::setTextFadeTimerCountdown(int16 countdown) { + if (countdown == -1) + countdown = 32000; + + _timer->setCountdown(31, countdown * 60); +} + +void KyraEngine_LoK::timerAsInvisibleTimeout(int timerNum) { + if (_brandonStatusBit & 0x20) { + checkAmuletAnimFlags(); + _timer->setCountdown(18, -1); + } +} + +void KyraEngine_LoK::timerAsWillowispTimeout(int timerNum) { + if (_brandonStatusBit & 0x2) { + checkAmuletAnimFlags(); + _timer->setCountdown(14, -1); + } +} + +void KyraEngine_LoK::timerRedrawAmulet(int timerNum) { + if (queryGameFlag(0xF1)) { + drawAmulet(); + _timer->setCountdown(19, -1); + } +} + +void KyraEngine_LoK::setWalkspeed(uint8 newSpeed) { + if (!_timer) + return; + + static const uint8 speeds[] = { 11, 9, 6, 5, 3 }; + + assert(newSpeed < ARRAYSIZE(speeds)); + _timer->setDelay(5, speeds[newSpeed]); +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/timer_lol.cpp b/engines/kyra/engine/timer_lol.cpp new file mode 100644 index 0000000000..8ece68afa4 --- /dev/null +++ b/engines/kyra/engine/timer_lol.cpp @@ -0,0 +1,206 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/engine/lol.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +#define TimerV2(x) new Common::Functor1Mem(this, &LoLEngine::x) + +void LoLEngine::setupTimers() { + _timer->addTimer(0, TimerV2(timerProcessDoors), 15, true); + _timer->addTimer(0x10, TimerV2(timerProcessMonsters), 6, true); + _timer->addTimer(0x11, TimerV2(timerProcessMonsters), 6, true); + _timer->setNextRun(0x11, _system->getMillis() + 3 * _tickLength); + _timer->addTimer(3, TimerV2(timerSpecialCharacterUpdate), 15, true); + _timer->addTimer(4, TimerV2(timerProcessFlyingObjects), 1, true); + _timer->addTimer(0x50, TimerV2(timerRunSceneAnimScript), 0, false); + _timer->addTimer(0x51, TimerV2(timerRunSceneAnimScript), 0, false); + _timer->addTimer(0x52, TimerV2(timerRunSceneAnimScript), 0, false); + _timer->addTimer(8, TimerV2(timerRegeneratePoints), 1200, true); + _timer->addTimer(9, TimerV2(timerUpdatePortraitAnimations), 10, true); + _timer->addTimer(10, TimerV2(timerUpdateLampState), 360, true); + _timer->addTimer(11, TimerV2(timerFadeMessageText), 360, false); + _timer->resetNextRun(); +} + +void LoLEngine::timerProcessMonsters(int timerNum) { + for (int i = timerNum & 0x0F; i < 30; i += 2) + updateMonster(&_monsters[i]); +} + +void LoLEngine::timerSpecialCharacterUpdate(int timerNum) { + int eventsLeft = 0; + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1)) + continue; + + for (int ii = 0; ii < 5; ii++) { + if (!(_characters[i].characterUpdateEvents[ii])) + continue; + + if (--_characters[i].characterUpdateDelay[ii] > 0) { + if (_characters[i].characterUpdateDelay[ii] > eventsLeft) + eventsLeft = _characters[i].characterUpdateDelay[ii]; + continue; + } + + switch (_characters[i].characterUpdateEvents[ii] - 1) { + case 0: + if (_characters[i].weaponHit) { + _characters[i].weaponHit = 0; + _characters[i].characterUpdateDelay[ii] = calcMonsterSkillLevel(i, 6); + if (_characters[i].characterUpdateDelay[ii] > eventsLeft) + eventsLeft = _characters[i].characterUpdateDelay[ii]; + } else { + _characters[i].flags &= 0xFFFB; + } + + gui_drawCharPortraitWithStats(i); + break; + + case 1: + _characters[i].damageSuffered = 0; + gui_drawCharPortraitWithStats(i); + break; + + case 2: + _characters[i].flags &= 0xFFBF; + gui_drawCharPortraitWithStats(i); + break; + + case 3: + eventsLeft = rollDice(1, 2); + if (inflictDamage(i, eventsLeft, 0x8000, 0, 0x80)) { + _txt->printMessage(2, getLangString(0x4022), _characters[i].name); + _characters[i].characterUpdateDelay[ii] = 10; + if (_characters[i].characterUpdateDelay[ii] > eventsLeft) + eventsLeft = _characters[i].characterUpdateDelay[ii]; + } + break; + + case 4: + _characters[i].flags &= 0xFEFF; + _txt->printMessage(0, getLangString(0x4027), _characters[i].name); + gui_drawCharPortraitWithStats(i); + break; + + case 5: + setTemporaryFaceFrame(i, 0, 0, 1); + break; + + case 6: + _characters[i].flags &= 0xEFFF; + gui_drawCharPortraitWithStats(i); + break; + + case 7: + restoreSwampPalette(); + break; + + default: + break; + } + + if (_characters[i].characterUpdateDelay[ii] <= 0) + _characters[i].characterUpdateEvents[ii] = 0; + } + } + + if (eventsLeft) + _timer->enable(3); + else + _timer->disable(3); +} + +void LoLEngine::timerProcessFlyingObjects(int timerNum) { + for (int i = 0; i < 8; i++) { + if (!_flyingObjects[i].enable) + continue; + updateFlyingObject(&_flyingObjects[i]); + } +} + +void LoLEngine::timerRunSceneAnimScript(int timerNum) { + runLevelScript(0x401 + (timerNum & 0x0F), -1); +} + +void LoLEngine::timerRegeneratePoints(int timerNum) { + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1)) + continue; + + // check for Duble ring + int hInc = (_characters[i].flags & 8) ? 0 : (itemEquipped(i, 228) ? 4 : 1); + // check for Talba ring + int mInc = _drainMagic ? ((_characters[i].magicPointsMax >> 5) * -1) : + ((_characters[i].flags & 8) ? 0 : (itemEquipped(i, 227) ? (_characters[i].magicPointsMax / 10) : 1)); + + _characters[i].magicPointsCur = CLIP(_characters[i].magicPointsCur + mInc, 0, _characters[i].magicPointsMax); + + if (!(_characters[i].flags & 0x80)) + increaseCharacterHitpoints(i, hInc, false); + + gui_drawCharPortraitWithStats(i); + } +} + +void LoLEngine::timerUpdatePortraitAnimations(int skipUpdate) { + if (skipUpdate != 1) + skipUpdate = 0; + + for (int i = 0; i < 4; i++) { + if (!(_characters[i].flags & 1) || (_characters[i].flags & 8) || (_characters[i].curFaceFrame > 1)) + continue; + + if (_characters[i].curFaceFrame != 1) { + if (--_characters[i].nextAnimUpdateCountdown <= 0 && !skipUpdate) { + _characters[i].curFaceFrame = 1; + gui_drawCharPortraitWithStats(i); + _timer->setCountdown(9, 10); + } + } else { + _characters[i].curFaceFrame = 0; + gui_drawCharPortraitWithStats(i); + _characters[i].nextAnimUpdateCountdown = rollDice(1, 12) + 6; + } + } +} + +void LoLEngine::timerUpdateLampState(int timerNum) { + if ((_flagsTable[31] & 0x08) && (_flagsTable[31] & 0x04) && _brightness && _lampOilStatus) + _lampOilStatus--; +} + +void LoLEngine::timerFadeMessageText(int timerNum) { + _timer->disable(timerNum); + initTextFading(0, 0); +} + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/engine/timer_mr.cpp b/engines/kyra/engine/timer_mr.cpp new file mode 100644 index 0000000000..544e36afa9 --- /dev/null +++ b/engines/kyra/engine/timer_mr.cpp @@ -0,0 +1,101 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_mr.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +#define TimerV3(x) new Common::Functor1Mem(this, &KyraEngine_MR::x) + +void KyraEngine_MR::setupTimers() { + _timer->addTimer(0, TimerV3(timerRestoreCommandLine), -1, 1); + for (int i = 1; i <= 3; ++i) + _timer->addTimer(i, TimerV3(timerRunSceneScript7), -1, 0); + _timer->addTimer(4, TimerV3(timerFleaDeath), -1, 0); + for (int i = 5; i <= 11; ++i) + _timer->addTimer(i, TimerV3(timerRunSceneScript7), -1, 0); + for (int i = 12; i <= 13; ++i) + _timer->addTimer(i, TimerV3(timerRunSceneScript7), 0, 0); +} + +void KyraEngine_MR::timerRestoreCommandLine(int arg) { + if (_shownMessage) + _restoreCommandLine = true; +} + +void KyraEngine_MR::timerRunSceneScript7(int arg) { + _emc->init(&_sceneScriptState, &_sceneScriptData); + _sceneScriptState.regs[1] = _mouseX; + _sceneScriptState.regs[2] = _mouseY; + _sceneScriptState.regs[3] = 0; + _sceneScriptState.regs[4] = _itemInHand; + _emc->start(&_sceneScriptState, 7); + + while (_emc->isValid(&_sceneScriptState)) + _emc->run(&_sceneScriptState); +} + +void KyraEngine_MR::timerFleaDeath(int arg) { + _timer->setCountdown(4, 5400); + saveGameStateIntern(999, "Autosave", 0); + _screen->hideMouse(); + _timer->disable(4); + runAnimationScript("FLEADTH1.EMC", 0, 0, 1, 1); + runAnimationScript("FLEADTH2.EMC", 0, 0, 1, 0); + showBadConscience(); + delay(60, true); + const char *str1 = (const char *)getTableEntry(_cCodeFile, 130); + const char *str2 = (const char *)getTableEntry(_cCodeFile, 131); + if (str1 && str2) { + badConscienceChat(str1, 204, 130); + badConscienceChat(str2, 204, 131); + } + delay(60, true); + hideBadConscience(); + runAnimationScript("FLEADTH3.EMC", 0, 0, 0, 1); + _deathHandler = 9; + _screen->showMouse(); +} + +void KyraEngine_MR::setWalkspeed(uint8 speed) { + if (speed < 5) + speed = 3; + else + speed = 5; + + _mainCharacter.walkspeed = speed; +} + +void KyraEngine_MR::setCommandLineRestoreTimer(int secs) { + if (secs == -1) + secs = 32000; + _timer->setCountdown(0, secs*60); +} + +void KyraEngine_MR::setNextIdleAnimTimer() { + _nextIdleAnim = _system->getMillis() + _rnd.getRandomNumberRng(10, 15) * 1000; +} + +} // End of namespace Kyra diff --git a/engines/kyra/engine/timer_rpg.cpp b/engines/kyra/engine/timer_rpg.cpp new file mode 100644 index 0000000000..572829eb64 --- /dev/null +++ b/engines/kyra/engine/timer_rpg.cpp @@ -0,0 +1,90 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#if defined(ENABLE_EOB) || defined(ENABLE_LOL) + +#include "kyra/engine/kyra_rpg.h" +#include "kyra/engine/timer.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraRpgEngine::enableSysTimer(int sysTimer) { + if (sysTimer != 2) + return; + + for (int i = 0; i < getNumClock2Timers(); i++) + _timer->pauseSingleTimer(getClock2Timer(i), false); +} + +void KyraRpgEngine::disableSysTimer(int sysTimer) { + if (sysTimer != 2) + return; + + for (int i = 0; i < getNumClock2Timers(); i++) + _timer->pauseSingleTimer(getClock2Timer(i), true); +} + +void KyraRpgEngine::enableTimer(int id) { + _timer->enable(id); + _timer->setCountdown(id, _timer->getDelay(id)); +} + +void KyraRpgEngine::timerProcessDoors(int timerNum) { + for (int i = 0; i < 3; i++) { + uint16 b = _openDoorState[i].block; + if (!b) + continue; + + int v = _openDoorState[i].state; + int c = _openDoorState[i].wall; + + _levelBlockProperties[b].walls[c] += v; + _levelBlockProperties[b].walls[c ^ 2] += v; + + int snd = 3; + int flg = _wllWallFlags[_levelBlockProperties[b].walls[c]]; + if (flg & 0x20) + snd = 5; + else if (v == -1) + snd = 4; + + if (_flags.gameID == GI_LOL) { + if (!(_updateFlags & 1)) { + snd_processEnvironmentalSoundEffect(snd + 28, b); + if (!checkSceneUpdateNeed(b)) + updateEnvironmentalSfx(0); + } + } else { + checkSceneUpdateNeed(b); + updateEnvironmentalSfx(snd); + } + + if (flg & 0x30) + _openDoorState[i].block = 0; + } +} + +} // namespace Kyra + +#endif diff --git a/engines/kyra/engine/util.cpp b/engines/kyra/engine/util.cpp new file mode 100644 index 0000000000..ae5b833858 --- /dev/null +++ b/engines/kyra/engine/util.cpp @@ -0,0 +1,148 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/util.h" + +namespace Kyra { + +int Util::decodeString1(const char *src, char *dst) { + static const uint8 decodeTable1[] = { + 0x20, 0x65, 0x74, 0x61, 0x69, 0x6E, 0x6F, 0x73, 0x72, 0x6C, 0x68, + 0x63, 0x64, 0x75, 0x70, 0x6D + }; + + static const uint8 decodeTable2[] = { + 0x74, 0x61, 0x73, 0x69, 0x6F, 0x20, 0x77, 0x62, 0x20, 0x72, 0x6E, + 0x73, 0x64, 0x61, 0x6C, 0x6D, 0x68, 0x20, 0x69, 0x65, 0x6F, 0x72, + 0x61, 0x73, 0x6E, 0x72, 0x74, 0x6C, 0x63, 0x20, 0x73, 0x79, 0x6E, + 0x73, 0x74, 0x63, 0x6C, 0x6F, 0x65, 0x72, 0x20, 0x64, 0x74, 0x67, + 0x65, 0x73, 0x69, 0x6F, 0x6E, 0x72, 0x20, 0x75, 0x66, 0x6D, 0x73, + 0x77, 0x20, 0x74, 0x65, 0x70, 0x2E, 0x69, 0x63, 0x61, 0x65, 0x20, + 0x6F, 0x69, 0x61, 0x64, 0x75, 0x72, 0x20, 0x6C, 0x61, 0x65, 0x69, + 0x79, 0x6F, 0x64, 0x65, 0x69, 0x61, 0x20, 0x6F, 0x74, 0x72, 0x75, + 0x65, 0x74, 0x6F, 0x61, 0x6B, 0x68, 0x6C, 0x72, 0x20, 0x65, 0x69, + 0x75, 0x2C, 0x2E, 0x6F, 0x61, 0x6E, 0x73, 0x72, 0x63, 0x74, 0x6C, + 0x61, 0x69, 0x6C, 0x65, 0x6F, 0x69, 0x72, 0x61, 0x74, 0x70, 0x65, + 0x61, 0x6F, 0x69, 0x70, 0x20, 0x62, 0x6D + }; + + int size = 0; + uint cChar = 0; + while ((cChar = *src++) != 0) { + if (cChar & 0x80) { + cChar &= 0x7F; + int index = (cChar & 0x78) >> 3; + *dst++ = decodeTable1[index]; + ++size; + assert(cChar < sizeof(decodeTable2)); + cChar = decodeTable2[cChar]; + } + + *dst++ = cChar; + ++size; + } + + *dst++ = 0; + return size; +} + +void Util::decodeString2(const char *src, char *dst) { + if (!src || !dst) + return; + + char out = 0; + while ((out = *src) != 0) { + if (*src == 0x1B) { + ++src; + out = *src + 0x7F; + } + *dst++ = out; + ++src; + } + + *dst = 0; +} + +void Util::convertDOSToISO(char *str) { + uint8 *s = (uint8 *)str; + + for (; *s; ++s) { + if (*s >= 128) { + uint8 c = _charMapDOSToISO[*s - 128]; + + if (!c) + c = 0x20; + + *s = c; + } + } +} + +void Util::convertISOToDOS(char *str) { + while (*str) + convertISOToDOS(*str++); +} + +void Util::convertISOToDOS(char &c) { + uint8 code = (uint8)c; + if (code >= 128) { + code = _charMapISOToDOS[code - 128]; + if (!code) + code = 0x20; + } + + c = code; +} + +// CP850 to ISO-8859-1 (borrowed from engines/saga/font_map.cpp) +const uint8 Util::_charMapDOSToISO[128] = { + 199, 252, 233, 226, 228, 224, 229, 231, 234, 235, 232, + 239, 238, 236, 196, 197, 201, 230, 198, 244, 246, 242, + 251, 249, 255, 214, 220, 248, 163, 216, 215, 0, 225, + 237, 243, 250, 241, 209, 170, 186, 191, 174, 172, 189, + 188, 161, 171, 187, 0, 0, 0, 0, 0, 193, 194, + 192, 169, 0, 0, 0, 0, 162, 165, 0, 0, 0, + 0, 0, 0, 0, 227, 195, 0, 0, 0, 0, 0, + 0, 0, 164, 240, 208, 202, 203, 200, 0, 205, 206, + 207, 0, 0, 0, 0, 166, 204, 0, 211, 223, 212, + 210, 245, 213, 181, 254, 222, 218, 219, 217, 253, 221, + 175, 180, 173, 177, 0, 190, 182, 167, 247, 184, 176, + 168, 183, 185, 179, 178, 0, 160 +}; + +// ISO-8859-1 to CP850 +const uint8 Util::_charMapISOToDOS[128] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, + 173, 189, 156, 207, 190, 221, 245, 249, 184, 166, 174, + 170, 240, 169, 238, 248, 241, 253, 252, 239, 230, 244, + 250, 247, 251, 167, 175, 172, 171, 243, 168, 183, 181, + 182, 199, 142, 143, 146, 128, 212, 144, 210, 211, 222, + 214, 215, 216, 209, 165, 227, 224, 226, 229, 153, 158, + 157, 235, 233, 234, 154, 237, 232, 225, 133, 160, 131, + 198, 132, 134, 145, 135, 138, 130, 136, 137, 141, 161, + 140, 139, 208, 164, 149, 162, 147, 228, 148, 246, 155, + 151, 163, 150, 129, 236, 231, 152 +}; + +} // End of namespace Kyra diff --git a/engines/kyra/engine/util.h b/engines/kyra/engine/util.h new file mode 100644 index 0000000000..130768f89d --- /dev/null +++ b/engines/kyra/engine/util.h @@ -0,0 +1,48 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_UTIL_H +#define KYRA_UTIL_H + +#include "common/scummsys.h" + +namespace Kyra { + +class Util { +public: + static int decodeString1(const char *src, char *dst); + static void decodeString2(const char *src, char *dst); + + // Since our current GUI font uses ISO-8859-1, this + // conversion functionallty uses that as a base. + static void convertDOSToISO(char *str); + static void convertISOToDOS(char *str); + static void convertISOToDOS(char &c); + +private: + static const uint8 _charMapDOSToISO[128]; + static const uint8 _charMapISOToDOS[128]; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/eob.cpp b/engines/kyra/eob.cpp deleted file mode 100644 index 6a6b20baac..0000000000 --- a/engines/kyra/eob.cpp +++ /dev/null @@ -1,573 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifdef ENABLE_EOB - -#include "kyra/eob.h" -#include "kyra/resource.h" -#include "kyra/sound.h" - -namespace Kyra { - -EoBEngine::EoBEngine(OSystem *system, const GameFlags &flags) - : EoBCoreEngine(system, flags) { - _numSpells = 53; - _menuChoiceInit = 4; - - _turnUndeadString = 0; - _finBonusStrings = _npcStrings[1] = _npcStrings[2] = 0; - _npcStrings[3] = _npcStrings[4] = _npcStrings[5] = _npcStrings[6] = 0; - _npcStrings[7] = _npcStrings[8] = _npcStrings[9] = _npcStrings[10] = 0; - _npcShpData = _npcSubShpIndex1 = _npcSubShpIndex2 = _npcSubShpY = 0; - _dscDoorScaleMult4 = _dscDoorScaleMult5 = _dscDoorScaleMult6 = _dscDoorY3 = 0; - _dscDoorY4 = _dscDoorY5 = _dscDoorY6 = _dscDoorY7 = _doorShapeEncodeDefs = 0; - _doorSwitchShapeEncodeDefs = _doorSwitchCoords = 0; - _dscDoorCoordsExt = 0; -} - -EoBEngine::~EoBEngine() { - delete[] _itemsOverlay; -} - -Common::Error EoBEngine::init() { - Common::Error err = EoBCoreEngine::init(); - if (err.getCode() != Common::kNoError) - return err; - - initStaticResource(); - - if (_configRenderMode != Common::kRenderCGA) - _itemsOverlay = _res->fileData((_configRenderMode == Common::kRenderEGA) ? "ITEMRMP.EGA" : "ITEMRMP.VGA", 0); - - _screen->modifyScreenDim(7, 0x01, 0xB3, 0x22, 0x12); - _screen->modifyScreenDim(9, 0x01, 0x7D, 0x26, 0x3F); - _screen->modifyScreenDim(12, 0x01, 0x04, 0x14, 0xA0); - - _scriptTimersCount = 1; - - if (_configRenderMode == Common::kRenderEGA) { - Palette pal(16); - _screen->loadPalette(_egaDefaultPalette, pal, 16); - _screen->setScreenPalette(pal); - } else { - _screen->loadPalette("PALETTE.COL", _screen->getPalette(0)); - } - - return Common::kNoError; -} - -void EoBEngine::startupNew() { - _currentLevel = 1; - _currentSub = 0; - loadLevel(1, 0); - _currentBlock = 490; - _currentDirection = 0; - setHandItem(0); - - EoBCoreEngine::startupNew(); -} - -void EoBEngine::startupLoad() { - _sound->loadSoundFile("ADLIB"); -} - -void EoBEngine::drawNpcScene(int npcIndex) { - _screen->copyRegion(0, 0, 0, 0, 176, 120, 6, 0, Screen::CR_NO_P_CHECK); - switch (npcIndex) { - case 0: - encodeDrawNpcSeqShape(2, 88, 104); - break; - - case 1: - if (_npcSequenceSub == -1) { - encodeDrawNpcSeqShape(0, 88, 104); - } else { - encodeDrawNpcSeqShape(0, 60, 104); - encodeDrawNpcSeqShape(5, 116, 104); - } - break; - - case 2: - if (_npcSequenceSub == -1) { - encodeDrawNpcSeqShape(3, 88, 104); - } else { - encodeDrawNpcSeqShape(3, 60, 104); - encodeDrawNpcSeqShape(_npcSubShpIndex1[_npcSequenceSub], 116, 104); - encodeDrawNpcSeqShape(_npcSubShpIndex2[_npcSequenceSub], 116, _npcSubShpY[_npcSequenceSub]); - } - break; - - case 3: - encodeDrawNpcSeqShape(7, 88, 104); - break; - - case 4: - encodeDrawNpcSeqShape(6, 88, 104); - break; - - case 5: - encodeDrawNpcSeqShape(18, 88, 88); - break; - - case 6: - encodeDrawNpcSeqShape(17, 88, 104); - break; - - case 7: - encodeDrawNpcSeqShape(4, 88, 104); - break; - - default: - break; - } -} - -void EoBEngine::encodeDrawNpcSeqShape(int npcIndex, int drawX, int drawY) { - const uint8 *shpDef = &_npcShpData[npcIndex << 2]; - _screen->_curPage = 2; - const uint8 *shp = _screen->encodeShape(shpDef[0], shpDef[1], shpDef[2], shpDef[3], false, _cgaMappingDefault); - _screen->_curPage = 0; - _screen->drawShape(0, shp, drawX - (shp[2] << 2), drawY - shp[1], 5); - delete[] shp; -} - -#define DLG2(txt, buttonstr) (runDialogue(txt, 2, _npcStrings[buttonstr][0], _npcStrings[buttonstr][1]) - 1) -#define DLG3(txt, buttonstr) (runDialogue(txt, 3, _npcStrings[buttonstr][0], _npcStrings[buttonstr][1], _npcStrings[buttonstr][2]) - 1) -#define DLG2A3(cond, txt, buttonstr1, buttonstr2) ((cond) ? (DLG2(txt, buttonstr1) ? 2 : 0) : DLG3(txt, buttonstr2)) -#define TXT(txt) _txt->printDialogueText(txt, _moreStrings[0]) - -void EoBEngine::runNpcDialogue(int npcIndex) { - int r = 0; - int a = 0; - Item itm = 0; - - switch (npcIndex) { - case 0: - for (r = 1; r == 1;) { - gui_drawDialogueBox(); - r = DLG2A3(checkScriptFlags(0x2000), 8, 2, 1); - if (r == 1) { - TXT(1); - setScriptFlags(0x2000); - } else if (r == 0) { - npcJoinDialogue(6, 12, 23, 2); - setScriptFlags(0x4000); - } - } - break; - - case 1: - if (!checkScriptFlags(0x10000)) { - if (checkScriptFlags(0x8000)) { - a = 13; - } else { - setScriptFlags(0x8000); - r = DLG2(3, 3); - a = 4; - } - if (!r) - r = DLG2(a, 4); - - if (!r) { - for (a = 0; a < 6; a++) - createItemOnCurrentBlock(55); - createItemOnCurrentBlock(62); - setScriptFlags(0x10000); - TXT(6); - npcJoinDialogue(7, 7, 29, 30); - } else { - TXT(5); - } - r = 1; - } - - if (!checkScriptFlags(0x80000)) { - for (a = 0; a < 6; a++) { - if (testCharacter(a, 1) && _characters[a].portrait == -9) - break; - } - if (a != 6) { - TXT(25); - TXT(26); - setScriptFlags(0x80000); - r = 1; - } - } - - if (!checkScriptFlags(0x100000)) { - if (deletePartyItems(6, -1)) { - _npcSequenceSub = 0; - drawNpcScene(npcIndex); - TXT(28); - createItemOnCurrentBlock(32); - setScriptFlags(0x100000); - r = 1; - } - } - - if (!r) - _txt->printDialogueText(_npcStrings[0][0], true); - - break; - - case 2: - if (checkScriptFlags(0x10000)) { - if (checkScriptFlags(0x20000)) { - TXT(11); - } else { - r = DLG2A3(!countResurrectionCandidates(), 9, 5, 6); - if (r < 2) { - if (r == 0) - healParty(); - else - resurrectionSelectDialogue(); - setScriptFlags(0x20000); - } - } - } else { - TXT(24); - } - break; - - case 3: - if (!DLG2(18, 7)) { - setScriptFlags(0x8400000); - for (a = 0; a < 30; a++) { - if (_monsters[a].mode == 8) - _monsters[a].mode = 5; - } - } else if (deletePartyItems(49, -1)) { - TXT(20); - setScriptFlags(0x400000); - } else { - TXT(19); - } - break; - - case 4: - r = DLG3(14, 8); - if (r == 0) - setScriptFlags(0x200000); - else if (r == 1) - TXT(15); - setScriptFlags(0x800000); - break; - - case 5: - if (!DLG2(16, 9)) { - TXT(17); - for (a = 0; a < 6; a++) { - for (r = 0; r < 2; r++) { - itm = _characters[a].inventory[r]; - if (itm && (_items[itm].type < 51 || _items[itm].type > 56)) { - _characters[a].inventory[r] = 0; - setItemPosition((Item *)&_levelBlockProperties[_currentBlock].drawObjects, _currentBlock, itm, _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]); - } - } - } - } - setScriptFlags(0x2000000); - break; - - case 6: - TXT(21); - setScriptFlags(0x1000000); - break; - - case 7: - r = DLG3(22, 10); - if (r < 2) { - if (r == 0) - npcJoinDialogue(8, 27, 44, 45); - else - TXT(31); - setScriptFlags(0x4000000); - } - break; - - default: - break; - } -} - -#undef TXT -#undef DLG2 -#undef DLG3 -#undef DLG2A3 - -void EoBEngine::updateUsedCharacterHandItem(int charIndex, int slot) { - EoBItem *itm = &_items[_characters[charIndex].inventory[slot]]; - if (itm->type == 48) { - int charges = itm->flags & 0x3F; - if (--charges) - --itm->flags; - else - deleteInventoryItem(charIndex, slot); - } else if (itm->type == 34 || itm->type == 35) { - deleteInventoryItem(charIndex, slot); - } -} - -void EoBEngine::replaceMonster(int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem) { - if (_levelBlockProperties[block].flags & 7) - return; - - for (int i = 0; i < 30; i++) { - if (_monsters[i].hitPointsCur <= 0) { - initMonster(i, unit, block, pos, dir, type, shpIndex, mode, h2, randItem, fixedItem); - break; - } - } -} - -bool EoBEngine::killMonsterExtra(EoBMonsterInPlay *m) { - if (m->type == 21) { - _playFinale = true; - _runFlag = false; - } - return true; -} - -void EoBEngine::updateScriptTimersExtra() { - int cnt = 0; - for (int i = 1; i < 30; i++) { - if (_monsters[i].hitPointsCur <= 0) - cnt++; - } - - if (!cnt) { - for (int i = 1; i < 30; i++) { - if (getBlockDistance(_monsters[i].block, _currentBlock) > 3) { - killMonster(&_monsters[i], true); - break; - } - } - } -} - -void EoBEngine::loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2) { - _screen->loadShapeSetBitmap("DOOR", 5, 3); - _screen->_curPage = 2; - - if (doorType1 != 0xFF) { - for (int i = 0; i < 3; i++) { - const uint8 *enc = &_doorShapeEncodeDefs[(doorType1 * 3 + i) << 2]; - _doorShapes[shapeId1 + i] = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); - enc = &_doorSwitchShapeEncodeDefs[(doorType1 * 3 + i) << 2]; - _doorSwitches[shapeId1 + i].shp = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); - _doorSwitches[shapeId1 + i].x = _doorSwitchCoords[doorType1 * 6 + i * 2]; - _doorSwitches[shapeId1 + i].y = _doorSwitchCoords[doorType1 * 6 + i * 2 + 1]; - } - } - - if (doorType2 != 0xFF) { - for (int i = 0; i < 3; i++) { - const uint8 *enc = &_doorShapeEncodeDefs[(doorType2 * 3 + i) << 2]; - _doorShapes[shapeId2 + i] = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); - enc = &_doorSwitchShapeEncodeDefs[(doorType2 * 3 + i) << 2]; - _doorSwitches[shapeId2 + i].shp = _screen->encodeShape(enc[0], enc[1], enc[2], enc[3], false, (_flags.gameID == GI_EOB1) ? _cgaMappingLevel[_cgaLevelMappingIndex[_currentLevel - 1]] : 0); - _doorSwitches[shapeId2 + i].x = _doorSwitchCoords[doorType2 * 6 + i * 2]; - _doorSwitches[shapeId2 + i].y = _doorSwitchCoords[doorType2 * 6 + i * 2 + 1]; - } - } - - _screen->_curPage = 0; -} - -void EoBEngine::drawDoorIntern(int type, int index, int x, int y, int w, int wall, int mDim, int16 y1, int16 y2) { - int shapeIndex = type + 2 - mDim; - uint8 *shp = _doorShapes[shapeIndex]; - if (!shp) - return; - - int d1 = 0; - int d2 = 0; - int v = 0; - const ScreenDim *td = _screen->getScreenDim(5); - - switch (_currentLevel) { - case 4: - case 5: - case 6: - y = _dscDoorY6[mDim] - shp[1]; - d1 = _dscDoorCoordsExt[index << 1] >> 3; - d2 = _dscDoorCoordsExt[(index << 1) + 1] >> 3; - if (_shpDmX1 > d1) - d1 = _shpDmX1; - if (_shpDmX2 < d2) - d2 = _shpDmX2; - _screen->modifyScreenDim(5, d1, td->sy, d2 - d1, td->h); - v = ((wall < 30) ? (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult3[mDim] : _dscDoorScaleMult4[mDim]) * -1; - v -= (shp[2] << 3); - drawBlockObject(0, 2, shp, x + v, y, 5); - v += (shp[2] << 3); - drawBlockObject(1, 2, shp, x - v, y, 5); - if (_wllShapeMap[wall] == -1) - drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w - v, _doorSwitches[shapeIndex].y, 5); - break; - - case 7: - case 8: - case 9: - y = _dscDoorY3[mDim] - _doorShapes[shapeIndex + 3][1]; - d1 = x - (_doorShapes[shapeIndex + 3][2] << 2); - x -= (shp[2] << 2); - drawBlockObject(0, 2, _doorShapes[shapeIndex + 3], d1, y, 5); - setDoorShapeDim(index, y1, y2, 5); - y = _dscDoorY3[mDim] - ((wall < 30) ? (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult1[mDim] : _dscDoorScaleMult2[mDim]); - drawBlockObject(0, 2, shp, x, y, 5); - if (_wllShapeMap[wall] == -1) - drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); - break; - - case 10: - case 11: - v = ((wall < 30) ? (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult5[mDim] : _dscDoorScaleMult6[mDim]) * -1; - x -= (shp[2] << 2); - y = _dscDoorY4[mDim] + v; - drawBlockObject(0, 2, shp, x, y + v, 5); - v = (v >> 3) + (v >> 2); - y = _dscDoorY5[mDim]; - drawBlockObject(0, 2, _doorShapes[shapeIndex + 3], x, y - v, 5); - if (_wllShapeMap[wall] == -1) - drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); - break; - - default: - y = (_currentLevel == 12 ? _dscDoorY6[mDim] : _dscDoorY1[mDim]) - shp[1]; - x -= (shp[2] << 2); - y -= (wall >= 30 ? _dscDoorScaleMult2[mDim] : (wall - _dscDoorScaleOffs[wall]) * _dscDoorScaleMult1[mDim]); - drawBlockObject(0, 2, shp, x, y, 5); - - if (_wllShapeMap[wall] == -1) - drawBlockObject(0, 2, _doorSwitches[shapeIndex].shp, _doorSwitches[shapeIndex].x + w, _doorSwitches[shapeIndex].y, 5); - break; - } -} - -void EoBEngine::turnUndeadAuto() { - if (_currentLevel != 2 && _currentLevel != 7) - return; - - int oc = _openBookChar; - - for (int i = 0; i < 6; i++) { - if (!testCharacter(i, 0x0D)) - continue; - - EoBCharacter *c = &_characters[i]; - - if (_itemTypes[_items[c->inventory[0]].type].extraProperties != 6 && _itemTypes[_items[c->inventory[1]].type].extraProperties != 6) - continue; - - int l = getCharacterLevelIndex(2, c->cClass); - if (l > -1) { - if (c->level[l] > _openBookCasterLevel) { - _openBookCasterLevel = c->level[l]; - _openBookChar = i; - } - } else { - l = getCharacterLevelIndex(4, c->cClass); - if (l > -1) { - if ((c->level[l] - 2) > _openBookCasterLevel) { - _openBookCasterLevel = (c->level[l] - 2); - _openBookChar = i; - } - } - } - } - - if (_openBookCasterLevel) - spellCallback_start_turnUndead(); - - _openBookChar = oc; - _openBookCasterLevel = 0; -} - -void EoBEngine::turnUndeadAutoHit() { - _txt->printMessage(_turnUndeadString[0], -1, _characters[_openBookChar].name); - sparkEffectOffensive(); -} - -bool EoBEngine::checkPartyStatusExtra() { - _screen->copyPage(0, 10); - int cd = _screen->curDimIndex(); - gui_drawBox(0, 121, 320, 80, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); - _txt->setupField(9, false); - _txt->printMessage(_menuStringsDefeat[0]); - while (!shouldQuit()) { - removeInputTop(); - if (checkInput(0, false, 0) & 0xFF) - break; - } - _screen->copyPage(10, 0); - _eventList.clear(); - _screen->setScreenDim(cd); - _txt->removePageBreakFlag(); - return true; -} - -int EoBEngine::resurrectionSelectDialogue() { - gui_drawDialogueBox(); - _txt->printDialogueText(_npcStrings[0][1]); - - int r = _rrId[runDialogue(-1, 9, _rrNames[0], _rrNames[1], _rrNames[2], _rrNames[3], _rrNames[4], _rrNames[5], _rrNames[6], _rrNames[7], _rrNames[8]) - 1]; - - if (r < 0) { - r = -r; - deletePartyItems(33, r); - _npcSequenceSub = r - 1; - drawNpcScene(2); - npcJoinDialogue(_npcSequenceSub, 32 + (_npcSequenceSub << 1), -1, 33 + (_npcSequenceSub << 1)); - } else { - _characters[r].hitPointsCur = _characters[r].hitPointsMax; - } - - return 1; -} - -void EoBEngine::healParty() { - int cnt = rollDice(1, 3, 2); - for (int i = 0; i < 6 && cnt; i++) { - if (testCharacter(i, 3)) - continue; - - _characters[i].flags &= ~4; - neutralizePoison(i); - - if (_characters[i].hitPointsCur >= _characters[i].hitPointsMax) - continue; - - cnt--; - _characters[i].hitPointsCur += rollDice(1, 8, 9); - if (_characters[i].hitPointsCur > _characters[i].hitPointsMax) - _characters[i].hitPointsCur = _characters[i].hitPointsMax; - } -} - -const KyraRpgGUISettings *EoBEngine::guiSettings() { - return (_configRenderMode == Common::kRenderCGA || _configRenderMode == Common::kRenderEGA) ? &_guiSettingsEGA : &_guiSettingsVGA; -} - -} // End of namespace Kyra - -#endif // ENABLE_EOB diff --git a/engines/kyra/eob.h b/engines/kyra/eob.h deleted file mode 100644 index 889144b705..0000000000 --- a/engines/kyra/eob.h +++ /dev/null @@ -1,125 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifdef ENABLE_EOB - -#ifndef KYRA_EOB1_H -#define KYRA_EOB1_H - -#include "kyra/eobcommon.h" - -namespace Kyra { - -class EoBEngine : public EoBCoreEngine { -friend class GUI_EoB; -friend class EoBIntroPlayer; -public: - EoBEngine(OSystem *system, const GameFlags &flags); - ~EoBEngine(); - -private: - // Init / Release - Common::Error init(); - void initStaticResource(); - void initSpells(); - - // Main Menu - int mainMenu(); - int mainMenuLoop(); - int _menuChoiceInit; - - // Main loop - void startupNew(); - void startupLoad(); - - // Intro/Outro - void seq_playIntro(); - void seq_playFinale(); - void seq_xdeath(); - - const char *const *_finBonusStrings; - - // characters - void drawNpcScene(int npcIndex); - void encodeDrawNpcSeqShape(int npcIndex, int drawX, int drawY); - void runNpcDialogue(int npcIndex); - - const uint8 *_npcShpData; - const uint8 *_npcSubShpIndex1; - const uint8 *_npcSubShpIndex2; - const uint8 *_npcSubShpY; - const char *const *_npcStrings[11]; - - // items - void updateUsedCharacterHandItem(int charIndex, int slot); - - // Monsters - void replaceMonster(int unit, uint16 block, int d, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem); - bool killMonsterExtra(EoBMonsterInPlay *m); - void updateScriptTimersExtra(); - - // Level - const uint8 *loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs) { return 0; } - void loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2); - void drawDoorIntern(int type, int index, int x, int y, int w, int wall, int mDim, int16 y1, int16 y2); - - const int16 *_dscDoorCoordsExt; - const uint8 *_dscDoorScaleMult4; - const uint8 *_dscDoorScaleMult5; - const uint8 *_dscDoorScaleMult6; - const uint8 *_dscDoorY3; - const uint8 *_dscDoorY4; - const uint8 *_dscDoorY5; - const uint8 *_dscDoorY6; - const uint8 *_dscDoorY7; - - const uint8 *_doorShapeEncodeDefs; - const uint8 *_doorSwitchShapeEncodeDefs; - const uint8 *_doorSwitchCoords; - - // Fight - static const uint8 _monsterAcHitChanceTbl1[]; - static const uint8 _monsterAcHitChanceTbl2[]; - - // Magic - void turnUndeadAuto(); - void turnUndeadAutoHit(); - - const char *const *_turnUndeadString; - - // Misc - bool checkPartyStatusExtra(); - int resurrectionSelectDialogue(); - void healParty(); - - const KyraRpgGUISettings *guiSettings(); - - static const KyraRpgGUISettings _guiSettingsVGA; - static const KyraRpgGUISettings _guiSettingsEGA; - static const uint8 _egaDefaultPalette[]; -}; - -} // End of namespace Kyra - -#endif - -#endif // ENABLE_EOB diff --git a/engines/kyra/eobcommon.cpp b/engines/kyra/eobcommon.cpp deleted file mode 100644 index 9f7715b37f..0000000000 --- a/engines/kyra/eobcommon.cpp +++ /dev/null @@ -1,2536 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifdef ENABLE_EOB - -#include "kyra/kyra_rpg.h" -#include "kyra/resource.h" -#include "engines/kyra/sound_intern.h" -#include "engines/kyra/sound_adlib.h" -#include "kyra/script_eob.h" -#include "kyra/timer.h" -#include "kyra/debugger.h" - -#include "common/config-manager.h" -#include "common/translation.h" - -#include "backends/keymapper/keymapper.h" - -namespace Kyra { - -const char *const EoBCoreEngine::kKeymapName = "eob"; - -EoBCoreEngine::EoBCoreEngine(OSystem *system, const GameFlags &flags) - : KyraRpgEngine(system, flags), _numLargeItemShapes(flags.gameID == GI_EOB1 ? 14 : 11), - _numSmallItemShapes(flags.gameID == GI_EOB1 ? 23 : 26), - _numThrownItemShapes(flags.gameID == GI_EOB1 ? 12 : 9), - _numItemIconShapes(flags.gameID == GI_EOB1 ? 89 : 112), - _teleporterWallId(flags.gameID == GI_EOB1 ? 52 : 44) { - - _screen = 0; - _gui = 0; - _debugger = 0; - - _playFinale = false; - _runFlag = true; - _configMouse = _config2431 = true; - _loading = false; - - _enableHiResDithering = false; - - _envAudioTimer = 0; - _flashShapeTimer = 0; - _drawSceneTimer = 0; - - _largeItemShapes = _smallItemShapes = _thrownItemShapes = _spellShapes = _firebeamShapes = 0; - _itemIconShapes = _wallOfForceShapes = _teleporterShapes = _sparkShapes = _compassShapes = 0; - _redSplatShape = _greenSplatShape = _deadCharShape = _disabledCharGrid = 0; - _blackBoxSmallGrid = _weaponSlotGrid = _blackBoxWideGrid = _lightningColumnShape = 0; - - _monsterAcHitChanceTable1 = _monsterAcHitChanceTable2 = 0; - - _monsterDustStrings = 0; - _enemyMageSpellList = 0; - _enemyMageSfx = 0; - _beholderSpellList = 0; - _beholderSfx = 0; - - _faceShapes = 0; - _characters = 0; - _items = 0; - _itemTypes = 0; - _itemNames = 0; - _itemInHand = -1; - _numItems = _numItemNames = 0; - - _castScrollSlot = 0; - _currentSub = 0; - - _itemsOverlay = 0; - - _partyEffectFlags = 0; - _lastUsedItem = 0; - - _levelDecorationRects = 0; - _doorSwitches = 0; - _monsterProps = 0; - _monsterDecorations = 0; - _monsterFlashOverlay = _monsterStoneOverlay = 0; - _monsters = 0; - _dstMonsterIndex = 0; - _preventMonsterFlash = false; - - _teleporterPulse = 0; - - _dscShapeCoords = 0; - _dscItemPosIndex = 0; - _dscItemShpX = 0; - _dscItemScaleIndex = 0; - _dscItemTileIndex = 0; - _dscItemShapeMap = 0; - _dscDoorScaleOffs = 0; - _dscDoorScaleMult1 = 0; - _dscDoorScaleMult2 = 0; - _dscDoorScaleMult3 = 0; - _dscDoorY1 = 0; - _dscDoorXE = 0; - - _greenFadingTable = _blueFadingTable = _lightBlueFadingTable = _blackFadingTable = _greyFadingTable = 0; - - _menuDefs = 0; - - _exchangeCharacterId = -1; - _charExchangeSwap = 0; - _configHpBarGraphs = true; - _configMouseBtSwap = false; - - memset(_dialogueLastBitmap, 0, 13); - _npcSequenceSub = 0; - _moveCounter = 0; - _partyResting = false; - - _flyingObjects = 0; - - _inf = 0; - _stepCounter = 0; - _stepsUntilScriptCall = 0; - _scriptTimersMode = 3; - _currentDirection = 0; - - _openBookSpellLevel = 0; - _openBookSpellSelectedItem = 0; - _openBookSpellListOffset = 0; - _openBookChar = _openBookCharBackup = _openBookCasterLevel = 0; - _openBookType = _openBookTypeBackup = 0; - _openBookSpellList = 0; - _openBookAvailableSpells = 0; - _activeSpellCharId = 0; - _activeSpellCharacterPos = 0; - _activeSpell = 0; - _characterSpellTarget = 0; - _returnAfterSpellCallback = false; - _spells = 0; - _spellAnimBuffer = 0; - _clericSpellOffset = 0; - _restPartyElapsedTime = 0; - _allowSkip = false; - _allowImport = false; - - _wallsOfForce = 0; - - _rrCount = 0; - memset(_rrNames, 0, 10 * sizeof(const char *)); - memset(_rrId, 0, 10 * sizeof(int8)); - - _mainMenuStrings = _levelGainStrings = _monsterSpecAttStrings = _characterGuiStringsHp = 0; - _characterGuiStringsWp = _characterGuiStringsWr = _characterGuiStringsSt = 0; - _characterGuiStringsIn = _characterStatusStrings7 = _characterStatusStrings8 = 0; - _characterStatusStrings9 = _characterStatusStrings12 = _characterStatusStrings13 = 0; - _classModifierFlags = _saveThrowLevelIndex = _saveThrowModDiv = _saveThrowModExt = 0; - _wandTypes = _drawObjPosIndex = _flightObjFlipIndex = _expObjectTblIndex = 0; - _expObjectShpStart = _expObjectTlMode = _expObjectAnimTbl1 = _expObjectAnimTbl2 = _expObjectAnimTbl3 = 0; - _monsterStepTable0 = _monsterStepTable1 = _monsterStepTable2 = _monsterStepTable3 = 0; - _projectileWeaponAmmoTypes = _flightObjShpMap = _flightObjSclIndex = 0; - _monsterCloseAttPosTable1 = _monsterCloseAttPosTable2 = _monsterCloseAttChkTable1 = 0; - _monsterCloseAttChkTable2 = _monsterCloseAttDstTable1 = _monsterCloseAttDstTable2 = 0; - _monsterProximityTable = _findBlockMonstersTable = _wallOfForceDsY = _wallOfForceDsNumW = 0; - _wallOfForceDsNumH = _wallOfForceShpId = _wllFlagPreset = _teleporterShapeCoords = 0; - _monsterCloseAttUnkTable = _monsterFrmOffsTable1 = _monsterFrmOffsTable2 = 0; - _monsterDirChangeTable = _portalSeq = 0; - _wallOfForceDsX = 0; - _expObjectAnimTbl1Size = _expObjectAnimTbl2Size = _expObjectAnimTbl3Size = 0; - _wllFlagPresetSize = _scriptTimersCount = _buttonList1Size = _buttonList2Size = 0; - _buttonList3Size = _buttonList4Size = _buttonList5Size = _buttonList6Size = 0; - _buttonList7Size = _buttonList8Size = 0; - _inventorySlotsY = _mnDef = 0; - _transferStringsScummVM = 0; - _buttonDefs = 0; - _npcPreset = 0; - _chargenStatStrings = _chargenRaceSexStrings = _chargenClassStrings = 0; - _chargenAlignmentStrings = _pryDoorStrings = _warningStrings = _ripItemStrings = 0; - _cursedString = _enchantedString = _magicObjectStrings = _magicObjectString5 = 0; - _patternSuffix = _patternGrFix1 = _patternGrFix2 = _validateArmorString = 0; - _validateCursedString = _validateNoDropString = _potionStrings = _wandStrings = 0; - _itemMisuseStrings = _suffixStringsRings = _suffixStringsPotions = 0; - _suffixStringsWands = _takenStrings = _potionEffectStrings = _yesNoStrings = 0; - _npcMaxStrings = _okStrings = _npcJoinStrings = _cancelStrings = 0; - _abortStrings = _saveLoadStrings = _mnWord = _mnPrompt = _bookNumbers = 0; - _mageSpellList = _clericSpellList = _spellNames = _magicStrings1 = 0; - _magicStrings2 = _magicStrings3 = _magicStrings4 = _magicStrings6 = 0; - _magicStrings7 = _magicStrings8 = _saveNamePatterns = 0; - _spellAnimBuffer = 0; - _sparkEffectDefSteps = _sparkEffectDefSubSteps = _sparkEffectDefShift = 0; - _sparkEffectDefAdd = _sparkEffectDefX = _sparkEffectDefY = _sparkEffectOfShift = 0; - _sparkEffectOfX = _sparkEffectOfY = _magicFlightObjectProperties = 0; - _turnUndeadEffect = _burningHandsDest = _coneOfColdGfxTbl = 0; - _sparkEffectOfFlags1 = _sparkEffectOfFlags2 = 0; - _coneOfColdDest1 = _coneOfColdDest2 = _coneOfColdDest3 = _coneOfColdDest4 = 0; - _coneOfColdGfxTblSize = 0; - _menuButtonDefs = 0; - _updateCharNum = 0; - _menuStringsMain = _menuStringsSaveLoad = _menuStringsOnOff = _menuStringsSpells = 0; - _menuStringsRest = _menuStringsDrop = _menuStringsExit = _menuStringsStarve = 0; - _menuStringsScribe = _menuStringsDrop2 = _menuStringsHead = _menuStringsPoison = 0; - _menuStringsMgc = _menuStringsPrefs = _menuStringsRest2 = _menuStringsRest3 = 0; - _menuStringsRest4 = _menuStringsDefeat = _menuStringsTransfer = _menuStringsSpec = 0; - _menuStringsSpellNo = _menuYesNoStrings = _2431Strings = _katakanaLines = _katakanaSelectStrings = 0; - _errorSlotEmptyString = _errorSlotNoNameString = _menuOkString = 0; - _spellLevelsMage = _spellLevelsCleric = _numSpellsCleric = _numSpellsWisAdj = _numSpellsPal = _numSpellsMage = 0; - _mnNumWord = _numSpells = _mageSpellListSize = _spellLevelsMageSize = _spellLevelsClericSize = 0; - _inventorySlotsX = _slotValidationFlags = _encodeMonsterShpTable = 0; - _cgaMappingDefault = _cgaMappingAlt = _cgaMappingInv = _cgaLevelMappingIndex = _cgaMappingItemsL = _cgaMappingItemsS = _cgaMappingThrown = _cgaMappingIcons = _cgaMappingDeco = 0; - memset(_cgaMappingLevel, 0, sizeof(_cgaMappingLevel)); - memset(_expRequirementTables, 0, sizeof(_expRequirementTables)); - memset(_saveThrowTables, 0, sizeof(_saveThrowTables)); - memset(_doorType, 0, sizeof(_doorType)); - memset(_noDoorSwitch, 0, sizeof(_noDoorSwitch)); - memset(_scriptTimers, 0, sizeof(_scriptTimers)); - memset(_monsterBlockPosArray, 0, sizeof(_monsterBlockPosArray)); - memset(_foundMonstersArray, 0, sizeof(_foundMonstersArray)); - -#define DWM0 _dscWallMapping.push_back(0) -#define DWM(x) _dscWallMapping.push_back(&_sceneDrawVar##x) - DWM0; DWM0; DWM(Down); DWM(Right); - DWM(Down); DWM(Right); DWM(Down); DWM0; - DWM(Down); DWM(Left); DWM(Down); DWM(Left); - DWM0; DWM0; DWM(Down); DWM(Right); - DWM(Down); DWM(Right); DWM(Down); DWM0; - DWM(Down); DWM(Left); DWM(Down); DWM(Left); - DWM(Down); DWM(Right); DWM(Down); DWM0; - DWM(Down); DWM(Left); DWM0; DWM(Right); - DWM(Down); DWM0; DWM0; DWM(Left); -#undef DWM -#undef DWM0 -} - -EoBCoreEngine::~EoBCoreEngine() { - releaseItemsAndDecorationsShapes(); - releaseTempData(); - - if (_faceShapes) { - for (int i = 0; i < 44; i++) { - if (_characters) { - for (int ii = 0; ii < 6; ii++) { - if (_characters[ii].faceShape == _faceShapes[i]) - _characters[ii].faceShape = 0; - } - } - delete[] _faceShapes[i]; - _faceShapes[i] = 0; - } - delete[] _faceShapes; - } - - if (_characters) { - for (int i = 0; i < 6; i++) - delete[] _characters[i].faceShape; - } - - delete[] _characters; - delete[] _items; - delete[] _itemTypes; - if (_itemNames) { - for (int i = 0; i < 130; i++) - delete[] _itemNames[i]; - } - delete[] _itemNames; - delete[] _flyingObjects; - - delete[] _monsterFlashOverlay; - delete[] _monsterStoneOverlay; - delete[] _monsters; - - if (_monsterDecorations) { - releaseMonsterShapes(0, 36); - delete[] _monsterShapes; - delete[] _monsterDecorations; - - for (int i = 0; i < 24; i++) - delete[] _monsterPalettes[i]; - delete[] _monsterPalettes; - } - - delete[] _monsterProps; - - if (_doorSwitches) { - releaseDoorShapes(); - delete[] _doorSwitches; - } - - releaseDecorations(); - delete[] _levelDecorationRects; - _dscWallMapping.clear(); - - delete[] _greenFadingTable; - delete[] _blueFadingTable; - delete[] _lightBlueFadingTable; - delete[] _blackFadingTable; - delete[] _greyFadingTable; - - delete[] _spells; - delete[] _spellAnimBuffer; - delete[] _wallsOfForce; - delete[] _buttonDefs; - - delete _gui; - _gui = 0; - delete _screen; - _screen = 0; - - delete[] _menuDefs; - _menuDefs = 0; - - delete _inf; - _inf = 0; - delete _timer; - _timer = 0; - delete _debugger; - _debugger = 0; - delete _txt; - _txt = 0; -} - -void EoBCoreEngine::initKeymap() { -#ifdef ENABLE_KEYMAPPER - Common::Keymapper *const mapper = _eventMan->getKeymapper(); - - // Do not try to recreate same keymap over again - if (mapper->getKeymap(kKeymapName) != 0) - return; - - Common::Keymap *const engineKeyMap = new Common::Keymap(kKeymapName); - - const Common::KeyActionEntry keyActionEntries[] = { - { Common::KeyState(Common::KEYCODE_UP), "MVF", _("Move Forward") }, - { Common::KeyState(Common::KEYCODE_DOWN), "MVB", _("Move Back") }, - { Common::KeyState(Common::KEYCODE_LEFT), "MVL", _("Move Left") }, - { Common::KeyState(Common::KEYCODE_RIGHT), "MVR", _("Move Right") }, - { Common::KeyState(Common::KEYCODE_HOME), "TL", _("Turn Left") }, - { Common::KeyState(Common::KEYCODE_PAGEUP), "TR", _("Turn Right") }, - { Common::KeyState(Common::KEYCODE_i), "INV", _("Open/Close Inventory") }, - { Common::KeyState(Common::KEYCODE_p), "SCE", _("Switch Inventory/Character screen") }, - { Common::KeyState(Common::KEYCODE_c), "CMP", _("Camp") }, - { Common::KeyState(Common::KEYCODE_SPACE), "CSP", _("Cast Spell") }, - // TODO: Spell cursor, but this needs more thought, since different - // game versions use different keycodes. - { Common::KeyState(Common::KEYCODE_1), "SL1", _("Spell Level 1") }, - { Common::KeyState(Common::KEYCODE_2), "SL2", _("Spell Level 2") }, - { Common::KeyState(Common::KEYCODE_3), "SL3", _("Spell Level 3") }, - { Common::KeyState(Common::KEYCODE_4), "SL4", _("Spell Level 4") }, - { Common::KeyState(Common::KEYCODE_5), "SL5", _("Spell Level 5") } - }; - - for (uint i = 0; i < ARRAYSIZE(keyActionEntries); ++i) { - Common::Action *const act = new Common::Action(engineKeyMap, keyActionEntries[i].id, keyActionEntries[i].description); - act->addKeyEvent(keyActionEntries[i].ks); - } - - if (_flags.gameID == GI_EOB2) { - Common::Action *const act = new Common::Action(engineKeyMap, "SL6", _("Spell Level 6")); - act->addKeyEvent(Common::KeyState(Common::KEYCODE_6)); - } - - mapper->addGameKeymap(engineKeyMap); -#endif -} - -Common::Error EoBCoreEngine::init() { - // In EOB the timer proc is directly invoked via interrupt 0x1C, 18.2 times per second. - // This makes a tick length of 54.94. - _tickLength = 55; - - if (ConfMan.hasKey("render_mode")) - _configRenderMode = Common::parseRenderMode(ConfMan.get("render_mode")); - - _enableHiResDithering = (_configRenderMode == Common::kRenderEGA && _flags.useHiRes); - - _screen = new Screen_EoB(this, _system); - assert(_screen); - _screen->setResolution(); - - _res = new Resource(this); - assert(_res); - _res->reset(); - - _staticres = new StaticResource(this); - assert(_staticres); - if (!_staticres->init()) - error("_staticres->init() failed"); - - // SoundTowns_Darkmoon requires initialized _staticres - if (_flags.platform == Common::kPlatformDOS) { - //MidiDriverType midiDriver = MidiDriver::detectDevice(MDT_PCSPK | MDT_ADLIB); - _sound = new SoundAdLibPC(this, _mixer); - } else if (_flags.platform == Common::kPlatformFMTowns) { - _sound = new SoundTowns_Darkmoon(this, _mixer); - } else if (_flags.platform == Common::kPlatformPC98) { - - } - - assert(_sound); - _sound->init(); - - // Setup volume settings (and read in all ConfigManager settings) - syncSoundSettings(); - - if (!_screen->init()) - error("screen()->init() failed"); - - if (ConfMan.hasKey("save_slot")) { - _gameToLoad = ConfMan.getInt("save_slot"); - if (!saveFileLoadable(_gameToLoad)) - _gameToLoad = -1; - } - - setupKeyMap(); - - _gui = new GUI_EoB(this); - assert(_gui); - _txt = new TextDisplayer_rpg(this, _screen); - assert(_txt); - _inf = new EoBInfProcessor(this, _screen); - assert(_inf); - _debugger = new Debugger_EoB(this); - assert(_debugger); - - _screen->loadFont(Screen::FID_6_FNT, "FONT6.FNT"); - _screen->loadFont(Screen::FID_8_FNT, "FONT8.FNT"); - - Common::Error err = KyraRpgEngine::init(); - if (err.getCode() != Common::kNoError) - return err; - - initButtonData(); - initMenus(); - initStaticResource(); - initSpells(); - - _timer = new TimerManager(this, _system); - assert(_timer); - setupTimers(); - - _wllVmpMap[1] = 1; - _wllVmpMap[2] = 2; - memset(&_wllVmpMap[3], 3, 20); - _wllVmpMap[23] = 4; - _wllVmpMap[24] = 5; - - memcpy(_wllWallFlags, _wllFlagPreset, _wllFlagPresetSize); - - memset(&_specialWallTypes[3], 1, 5); - memset(&_specialWallTypes[13], 1, 5); - _specialWallTypes[8] = _specialWallTypes[18] = 6; - - memset(&_wllShapeMap[3], -1, 5); - memset(&_wllShapeMap[13], -1, 5); - - _wllVcnOffset = (_flags.platform == Common::kPlatformFMTowns) ? 0 : 16; - int bpp = (_flags.platform == Common::kPlatformFMTowns) ? 2 : 1; - - _greenFadingTable = new uint8[256 * bpp]; - _blueFadingTable = new uint8[256 * bpp]; - _lightBlueFadingTable = new uint8[256 * bpp]; - _blackFadingTable = new uint8[256 * bpp]; - _greyFadingTable = new uint8[256 * bpp]; - - _monsters = new EoBMonsterInPlay[30]; - memset(_monsters, 0, 30 * sizeof(EoBMonsterInPlay)); - - _characters = new EoBCharacter[6]; - memset(_characters, 0, sizeof(EoBCharacter) * 6); - - _items = new EoBItem[600]; - memset(_items, 0, sizeof(EoBItem) * 600); - - _itemNames = new char*[130]; - for (int i = 0; i < 130; i++) { - _itemNames[i] = new char[35]; - memset(_itemNames[i], 0, 35); - } - - _flyingObjects = new EoBFlyingObject[_numFlyingObjects]; - _flyingObjectsPtr = _flyingObjects; - memset(_flyingObjects, 0, _numFlyingObjects * sizeof(EoBFlyingObject)); - - int bufferSize = _flags.useHiColorMode ? 8192 : 4096; - _spellAnimBuffer = new uint8[bufferSize]; - memset(_spellAnimBuffer, 0, bufferSize); - - _wallsOfForce = new WallOfForce[5]; - memset(_wallsOfForce, 0, 5 * sizeof(WallOfForce)); - - memset(_doorType, 0, sizeof(_doorType)); - memset(_noDoorSwitch, 0, sizeof(_noDoorSwitch)); - - _monsterShapes = new uint8*[36]; - memset(_monsterShapes, 0, 36 * sizeof(uint8 *)); - _monsterDecorations = new SpriteDecoration[36]; - memset(_monsterDecorations, 0, 36 * sizeof(SpriteDecoration)); - _monsterPalettes = new uint8*[24]; - for (int i = 0; i < 24; i++) - _monsterPalettes[i] = new uint8[16]; - - _doorSwitches = new SpriteDecoration[6]; - memset(_doorSwitches, 0, 6 * sizeof(SpriteDecoration)); - - _monsterFlashOverlay = new uint8[16]; - _monsterStoneOverlay = new uint8[16]; - memset(_monsterFlashOverlay, (_configRenderMode == Common::kRenderCGA) ? 0xFF : 0x0F, 16 * sizeof(uint8)); - memset(_monsterStoneOverlay, 0x0D, 16 * sizeof(uint8)); - _monsterFlashOverlay[0] = _monsterStoneOverlay[0] = 0; - - // Prevent autosave on game startup - _lastAutosave = _system->getMillis(); - -#ifdef ENABLE_KEYMAPPER - _eventMan->getKeymapper()->pushKeymap(kKeymapName, true); -#endif - - return Common::kNoError; -} - -Common::Error EoBCoreEngine::go() { - _debugger->initialize(); - _txt->removePageBreakFlag(); - _screen->setFont(Screen::FID_8_FNT); - loadItemsAndDecorationsShapes(); - _screen->setMouseCursor(0, 0, _itemIconShapes[0]); - - // Import original save game files (especially the "Quick Start Party") - if (ConfMan.getBool("importOrigSaves")) { - importOriginalSaveFile(-1); - ConfMan.setBool("importOrigSaves", false); - ConfMan.flushToDisk(); - } - - loadItemDefs(); - int action = 0; - - for (bool repeatLoop = true; repeatLoop; repeatLoop ^= true) { - action = 0; - - if (_gameToLoad != -1) { - _sound->selectAudioResourceSet(kMusicIngame); - if (loadGameState(_gameToLoad).getCode() != Common::kNoError) - error("Couldn't load game slot %d on startup", _gameToLoad); - startupLoad(); - _gameToLoad = -1; - } else { - _screen->showMouse(); - action = mainMenu(); - } - - _sound->selectAudioResourceSet(kMusicIngame); - - if (action == -1) { - // load game - repeatLoop = _gui->runLoadMenu(72, 14); - if (repeatLoop && !shouldQuit()) - startupLoad(); - } else if (action == -2) { - // new game - repeatLoop = startCharacterGeneration(); - if (repeatLoop && !shouldQuit()) - startupNew(); - } else if (action == -3) { - // transfer party - repeatLoop = startPartyTransfer(); - if (repeatLoop && !shouldQuit()) - startupNew(); - } - } - - if (!shouldQuit() && action >= -3) { - runLoop(); - - if (_playFinale) { - // make final save for party transfer - saveGameStateIntern(-1, 0, 0); - _sound->selectAudioResourceSet(kMusicFinale); - seq_playFinale(); - } - } - - return Common::kNoError; -} - -void EoBCoreEngine::registerDefaultSettings() { - KyraEngine_v1::registerDefaultSettings(); - ConfMan.registerDefault("hpbargraphs", true); - ConfMan.registerDefault("mousebtswap", false); - ConfMan.registerDefault("importOrigSaves", true); -} - -void EoBCoreEngine::readSettings() { - _configHpBarGraphs = ConfMan.getBool("hpbargraphs"); - _configMouseBtSwap = ConfMan.getBool("mousebtswap"); - _configSounds = ConfMan.getBool("sfx_mute") ? 0 : 1; - _configMusic = _configSounds ? 1 : 0; - - if (_sound) - _sound->enableSFX(_configSounds); -} - -void EoBCoreEngine::writeSettings() { - ConfMan.setBool("hpbargraphs", _configHpBarGraphs); - ConfMan.setBool("mousebtswap", _configMouseBtSwap); - ConfMan.setBool("sfx_mute", _configSounds == 0); - - if (_sound) { - if (!_configSounds) - _sound->haltTrack(); - _sound->enableMusic(_configSounds ? 1 : 0); - _sound->enableSFX(_configSounds); - } - - ConfMan.flushToDisk(); -} - -void EoBCoreEngine::startupNew() { - gui_setPlayFieldButtons(); - _screen->_curPage = 0; - gui_drawPlayField(false); - _screen->_curPage = 0; - gui_drawAllCharPortraitsWithStats(); - drawScene(1); - _updateFlags = 0; - _updateCharNum = 0; -} - -void EoBCoreEngine::runLoop() { - _envAudioTimer = _system->getMillis() + (rollDice(1, 10, 3) * 18 * _tickLength); - _flashShapeTimer = 0; - _drawSceneTimer = _system->getMillis(); - - _screen->setFont(Screen::FID_6_FNT); - _screen->setScreenDim(7); - - _runFlag = true; - - while (!shouldQuit() && _runFlag) { - checkPartyStatus(true); - checkInput(_activeButtons, true, 0); - removeInputTop(); - - if (!_runFlag) - break; - - _timer->update(); - updateScriptTimers(); - updateWallOfForceTimers(); - - if (_sceneUpdateRequired) - drawScene(1); - - if (_envAudioTimer >= _system->getMillis() || (_flags.gameID == GI_EOB1 && (_currentLevel == 0 || _currentLevel > 3))) - continue; - - _envAudioTimer = _system->getMillis() + (rollDice(1, 10, 3) * 18 * _tickLength); - snd_processEnvironmentalSoundEffect(_flags.gameID == GI_EOB1 ? 30 : (rollDice(1, 2, -1) ? 27 : 28), _currentBlock + rollDice(1, 12, -1)); - updateEnvironmentalSfx(0); - turnUndeadAuto(); - } -} - -bool EoBCoreEngine::checkPartyStatus(bool handleDeath) { - int numChars = 0; - for (int i = 0; i < 6; i++) - numChars += testCharacter(i, 13); - - if (numChars) - return false; - - if (!handleDeath) - return true; - - gui_drawAllCharPortraitsWithStats(); - - if (checkPartyStatusExtra()) { - _screen->setFont(Screen::FID_8_FNT); - gui_updateControls(); - if (_gui->runLoadMenu(0, 0)) { - _screen->setFont(Screen::FID_6_FNT); - return true; - } - } - - quitGame(); - return false; -} - -void EoBCoreEngine::loadItemsAndDecorationsShapes() { - releaseItemsAndDecorationsShapes(); - int div = (_flags.gameID == GI_EOB1) ? 3 : 8; - int mul = (_flags.gameID == GI_EOB1) ? 64 : 24; - int size = 0; - - _largeItemShapes = new const uint8*[_numLargeItemShapes]; - if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { - for (int i = 0; i < _numLargeItemShapes; i++) - _largeItemShapes[i] = _staticres->loadRawData(kEoB2LargeItemsShapeData00 + i, size); - } else { - _screen->loadShapeSetBitmap("ITEML1", 5, 3); - for (int i = 0; i < _numLargeItemShapes; i++) - _largeItemShapes[i] = _screen->encodeShape((i / div) << 3, (i % div) * mul, 8, 24, false, _cgaMappingItemsL); - } - - _smallItemShapes = new const uint8*[_numSmallItemShapes]; - if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { - for (int i = 0; i < _numSmallItemShapes; i++) - _smallItemShapes[i] = _staticres->loadRawData(kEoB2SmallItemsShapeData00 + i, size); - } else { - _screen->loadShapeSetBitmap("ITEMS1", 5, 3); - for (int i = 0; i < _numSmallItemShapes; i++) - _smallItemShapes[i] = _screen->encodeShape((i / div) << 2, (i % div) * mul, 4, 24, false, _cgaMappingItemsS); - } - - _thrownItemShapes = new const uint8*[_numThrownItemShapes]; - _spellShapes = new const uint8*[4]; - _firebeamShapes = new const uint8*[3]; - if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { - for (int i = 0; i < _numThrownItemShapes; i++) - _thrownItemShapes[i] = _staticres->loadRawData(kEoB2ThrownShapeData00 + i, size); - for (int i = 0; i < 4; i++) - _spellShapes[i] = _staticres->loadRawData(kEoB2SpellShapeData00 + i, size); - for (int i = 0; i < 3; i++) - _firebeamShapes[i] = _staticres->loadRawData(kEoB2FirebeamShapeData00 + i, size); - _redSplatShape = _staticres->loadRawData(kEoB2RedSplatShapeData, size); - _greenSplatShape = _staticres->loadRawData(kEoB2GreenSplatShapeData, size); - } else { - _screen->loadShapeSetBitmap("THROWN", 5, 3); - for (int i = 0; i < _numThrownItemShapes; i++) - _thrownItemShapes[i] = _screen->encodeShape((i / div) << 2, (i % div) * mul, 4, 24, false, _cgaMappingThrown); - for (int i = 0; i < 4; i++) - _spellShapes[i] = _screen->encodeShape(8, i << 5, 6, 32, false, _cgaMappingThrown); - - _firebeamShapes[0] = _screen->encodeShape(16, 0, 4, 24, false, _cgaMappingThrown); - _firebeamShapes[1] = _screen->encodeShape(16, 24, 4, 24, false, _cgaMappingThrown); - _firebeamShapes[2] = _screen->encodeShape(16, 48, 3, 24, false, _cgaMappingThrown); - _redSplatShape = _screen->encodeShape(16, _flags.gameID == GI_EOB1 ? 144 : 72, 5, 24, false, _cgaMappingThrown); - _greenSplatShape = _screen->encodeShape(16, _flags.gameID == GI_EOB1 ? 168 : 96, 5, 16, false, _cgaMappingThrown); - } - - _itemIconShapes = new const uint8*[_numItemIconShapes]; - if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { - for (int i = 0; i < _numItemIconShapes; i++) - _itemIconShapes[i] = _staticres->loadRawData(kEoB2ItemIconShapeData00 + i, size); - } else { - _screen->loadShapeSetBitmap("ITEMICN", 5, 3); - for (int i = 0; i < _numItemIconShapes; i++) - _itemIconShapes[i] = _screen->encodeShape((i % 0x14) << 1, (i / 0x14) << 4, 2, 0x10, false, _cgaMappingIcons); - } - - _teleporterShapes = new const uint8*[6]; - _sparkShapes = new const uint8*[3]; - _compassShapes = new const uint8*[12]; - if (_flags.gameID == GI_EOB2) - _wallOfForceShapes = new const uint8*[6]; - - if (_flags.platform == Common::kPlatformFMTowns && _flags.gameID == GI_EOB2) { - _lightningColumnShape = _staticres->loadRawData(kEoB2LightningColumnShapeData, size); - for (int i = 0; i < 6; i++) - _wallOfForceShapes[i] = _staticres->loadRawData(kEoB2WallOfForceShapeData00 + i, size); - for (int i = 0; i < 6; i++) - _teleporterShapes[i] = _staticres->loadRawData(kEoB2TeleporterShapeData00 + i, size); - for (int i = 0; i < 3; i++) - _sparkShapes[i] = _staticres->loadRawData(kEoB2SparkShapeData00 + i, size); - for (int i = 0; i < 12; i++) - _compassShapes[i] = _staticres->loadRawData(kEoB2CompassShapeData00 + i, size); - - _deadCharShape = _staticres->loadRawData(kEoB2DeadCharShapeData, size); - _disabledCharGrid = _staticres->loadRawData(kEoB2DisabledCharGridShapeData, size); - _blackBoxSmallGrid = _staticres->loadRawData(kEoB2SmallGridShapeData, size); - _weaponSlotGrid = _staticres->loadRawData(kEoB2WeaponSlotGridShapeData, size); - _blackBoxWideGrid = _staticres->loadRawData(kEoB2WideGridShapeData, size); - - } else { - _screen->loadShapeSetBitmap("DECORATE", 5, 3); - if (_flags.gameID == GI_EOB2) { - _lightningColumnShape = _screen->encodeShape(18, 88, 4, 64); - for (int i = 0; i < 6; i++) - _wallOfForceShapes[i] = _screen->encodeShape(_wallOfForceShapeDefs[(i << 2)], _wallOfForceShapeDefs[(i << 2) + 1], _wallOfForceShapeDefs[(i << 2) + 2], _wallOfForceShapeDefs[(i << 2) + 3]); - } - - for (int i = 0; i < 6; i++) - _teleporterShapes[i] = _screen->encodeShape(_teleporterShapeDefs[(i << 2)], _teleporterShapeDefs[(i << 2) + 1], _teleporterShapeDefs[(i << 2) + 2], _teleporterShapeDefs[(i << 2) + 3], false, _cgaMappingDefault); - - _sparkShapes[0] = _screen->encodeShape(29, 0, 2, 16, false, _cgaMappingDeco); - _sparkShapes[1] = _screen->encodeShape(31, 0, 2, 16, false, _cgaMappingDeco); - _sparkShapes[2] = _screen->encodeShape(33, 0, 2, 16, false, _cgaMappingDeco); - _deadCharShape = _screen->encodeShape(0, 88, 4, 32, false, _cgaMappingDeco); - _disabledCharGrid = _screen->encodeShape(4, 88, 4, 32, false, _cgaMappingDeco); - _blackBoxSmallGrid = _screen->encodeShape(9, 88, 2, 8, false, _cgaMappingDeco); - _weaponSlotGrid = _screen->encodeShape(8, 88, 4, 16, false, _cgaMappingDeco); - _blackBoxWideGrid = _screen->encodeShape(8, 104, 4, 8, false, _cgaMappingDeco); - - static const uint8 dHeight[] = { 17, 10, 10 }; - static const uint8 dY[] = { 120, 137, 147 }; - - for (int y = 0; y < 3; y++) { - for (int x = 0; x < 4; x++) - _compassShapes[(y << 2) + x] = _screen->encodeShape(x * 3, dY[y], 3, dHeight[y], false, _cgaMappingDeco); - } - } -} - -void EoBCoreEngine::releaseItemsAndDecorationsShapes() { - if (_flags.platform != Common::kPlatformFMTowns || _flags.gameID != GI_EOB2) { - if (_largeItemShapes) { - for (int i = 0; i < _numLargeItemShapes; i++) { - if (_largeItemShapes[i]) - delete[] _largeItemShapes[i]; - } - } - - if (_smallItemShapes) { - for (int i = 0; i < _numSmallItemShapes; i++) { - if (_smallItemShapes[i]) - delete[] _smallItemShapes[i]; - } - } - - if (_thrownItemShapes) { - for (int i = 0; i < _numThrownItemShapes; i++) { - if (_thrownItemShapes[i]) - delete[] _thrownItemShapes[i]; - } - } - - if (_spellShapes) { - for (int i = 0; i < 4; i++) { - if (_spellShapes[i]) - delete[] _spellShapes[i]; - } - } - - if (_itemIconShapes) { - for (int i = 0; i < _numItemIconShapes; i++) { - if (_itemIconShapes[i]) - delete[] _itemIconShapes[i]; - } - } - - if (_sparkShapes) { - for (int i = 0; i < 3; i++) { - if (_sparkShapes[i]) - delete[] _sparkShapes[i]; - } - } - - if (_wallOfForceShapes) { - for (int i = 0; i < 6; i++) { - if (_wallOfForceShapes[i]) - delete[] _wallOfForceShapes[i]; - } - } - - if (_teleporterShapes) { - for (int i = 0; i < 6; i++) { - if (_teleporterShapes[i]) - delete[] _teleporterShapes[i]; - } - } - - if (_compassShapes) { - for (int i = 0; i < 12; i++) { - if (_compassShapes[i]) - delete[] _compassShapes[i]; - } - } - - if (_firebeamShapes) { - for (int i = 0; i < 3; i++) { - if (_firebeamShapes[i]) - delete[] _firebeamShapes[i]; - } - } - - delete[] _redSplatShape; - delete[] _greenSplatShape; - delete[] _deadCharShape; - delete[] _disabledCharGrid; - delete[] _blackBoxSmallGrid; - delete[] _weaponSlotGrid; - delete[] _blackBoxWideGrid; - delete[] _lightningColumnShape; - } - - delete[] _largeItemShapes; - delete[] _smallItemShapes; - delete[] _thrownItemShapes; - delete[] _spellShapes; - delete[] _itemIconShapes; - delete[] _sparkShapes; - delete[] _wallOfForceShapes; - delete[] _teleporterShapes; - delete[] _compassShapes; - delete[] _firebeamShapes; -} - -void EoBCoreEngine::setHandItem(Item itemIndex) { - if (itemIndex == -1) { - if (_flags.platform == Common::kPlatformFMTowns) - _screen->setMouseCursor(8, 8, _itemIconShapes[37], 0); - return; - } - - if (_screen->curDimIndex() == 7 && itemIndex) { - printFullItemName(itemIndex); - _txt->printMessage(_takenStrings[0]); - } - - _itemInHand = itemIndex; - int icon = _items[_itemInHand].icon; - const uint8 *shp = _itemIconShapes[icon]; - const uint8 *ovl = 0; - - if (icon && (_items[_itemInHand].flags & 0x80) && (_partyEffectFlags & 2)) - ovl = _flags.gameID == GI_EOB1 ? ((_configRenderMode == Common::kRenderCGA) ? _itemsOverlayCGA : &_itemsOverlay[icon << 4]) : _screen->generateShapeOverlay(shp, _lightBlueFadingTable); - - int mouseOffs = itemIndex ? 8 : 0; - _screen->setMouseCursor(mouseOffs, mouseOffs, shp, ovl); - - if (_flags.useHiColorMode) { - _screen->setFadeTable(_greyFadingTable); - _screen->setShapeFadingLevel(0); - } -} - -int EoBCoreEngine::getDexterityArmorClassModifier(int dexterity) { - static const int8 mod[] = { 5, 5, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, -1, -2, -3, -4, -4, -5, -5, -5, -6, -6 }; - return mod[dexterity]; -} - -int EoBCoreEngine::generateCharacterHitpointsByLevel(int charIndex, int levelIndex) { - EoBCharacter *c = &_characters[charIndex]; - int m = getClassAndConstHitpointsModifier(c->cClass, c->constitutionCur); - - int h = 0; - - for (int i = 0; i < 3; i++) { - if (!(levelIndex & (1 << i))) - continue; - - int d = getCharacterClassType(c->cClass, i); - - if (c->level[i] <= _hpIncrPerLevel[6 + i]) - h += rollDice(1, (d >= 0) ? _hpIncrPerLevel[d] : 0); - else - h += _hpIncrPerLevel[12 + i]; - - h += m; - } - - h /= _numLevelsPerClass[c->cClass]; - - if (h < 1) - h = 1; - - return h; -} - -int EoBCoreEngine::getClassAndConstHitpointsModifier(int cclass, int constitution) { - int res = _hpConstModifiers[constitution]; - // This also applies to EOB1 despite being coded differently there - if (res <= 2 || (_classModifierFlags[cclass] & 0x31)) - return res; - - return 2; -} - -int EoBCoreEngine::getCharacterClassType(int cclass, int levelIndex) { - return _characterClassType[cclass * 3 + levelIndex]; -} - -int EoBCoreEngine::getModifiedHpLimits(int hpModifier, int constModifier, int level, bool mode) { - int s = _hpIncrPerLevel[6 + hpModifier] > level ? level : _hpIncrPerLevel[6 + hpModifier]; - int res = s; - - if (!mode) - res *= (hpModifier >= 0 ? _hpIncrPerLevel[hpModifier] : 0); - - if (level > s) { - s = level - s; - res += (s * _hpIncrPerLevel[12 + hpModifier]); - } - - if (!mode || (constModifier > 0)) - res += (level * constModifier); - - return res; -} - -Common::String EoBCoreEngine::getCharStrength(int str, int strExt) { - if (strExt) { - if (strExt == 100) - strExt = 0; - _strenghtStr = Common::String::format("%d/%02d", str, strExt); - } else { - _strenghtStr = Common::String::format("%d", str); - } - - return _strenghtStr; -} - -int EoBCoreEngine::testCharacter(int16 index, int flags) { - if (index == -1) - return 0; - - EoBCharacter *c = &_characters[index]; - int res = 1; - - if (flags & 1) - res &= (c->flags & 1); - - if (flags & 2) - res &= ((c->hitPointsCur <= -10) || (c->flags & 8)) ? 0 : 1; - - if (flags & 4) - res &= ((c->hitPointsCur <= 0) || (c->flags & 8)) ? 0 : 1; - - if (flags & 8) - res &= (c->flags & 12) ? 0 : 1; - - if (flags & 0x20) - res &= (c->flags & 4) ? 0 : 1; - - if (flags & 0x10) - res &= (c->flags & 2) ? 0 : 1; - - if (flags & 0x40) - res &= (c->food <= 0) ? 0 : 1; - - return res; -} - -int EoBCoreEngine::getNextValidCharIndex(int curCharIndex, int searchStep) { - do { - curCharIndex += searchStep; - if (curCharIndex < 0) - curCharIndex = 5; - if (curCharIndex > 5) - curCharIndex = 0; - } while (!testCharacter(curCharIndex, 1)); - - return curCharIndex; -} - -void EoBCoreEngine::recalcArmorClass(int index) { - EoBCharacter *c = &_characters[index]; - int acm = getDexterityArmorClassModifier(c->dexterityCur); - c->armorClass = 10 + acm; - - static uint8 slot[] = { 17, 0, 1, 18 }; - for (int i = 0; i < 4; i++) { - int itm = c->inventory[slot[i]]; - if (!itm) - continue; - - if (i == 2) { - if (!validateWeaponSlotItem(index, 1)) - continue; - } - - int tp = _items[itm].type; - - if (!(_itemTypes[tp].allowedClasses & _classModifierFlags[c->cClass]) || (_itemTypes[tp].extraProperties & 0x7F) || (i >= 1 && i <= 2 && tp != 27 && !(_flags.gameID == GI_EOB2 && tp == 57))) - continue; - - c->armorClass += _itemTypes[tp].armorClass; - c->armorClass -= _items[itm].value; - } - - if (!_items[c->inventory[17]].value) { - int8 m1 = 0; - int8 m2 = 0; - - if (c->inventory[25]) { - if (!(_itemTypes[_items[c->inventory[25]].type].extraProperties & 0x7F)) - m1 = _items[c->inventory[25]].value; - } - - if (c->inventory[26]) { - if (!(_itemTypes[_items[c->inventory[26]].type].extraProperties & 0x7F)) - m2 = _items[c->inventory[26]].value; - } - - c->armorClass -= MAX(m1, m2); - } - - if (c->effectsRemainder[0] > 0) { - if (c->armorClass <= (acm + 6)) - c->effectsRemainder[0] = 0; - else - c->armorClass = (acm + 6); - } - - // shield - if ((c->effectFlags & 8) && (c->armorClass > 4)) - c->armorClass = 4; - - // magical vestment - if (c->effectFlags & 0x4000) { - int8 m1 = 5; - - if (getClericPaladinLevel(index) > 5) - m1 += ((getClericPaladinLevel(index) - 5) / 3); - - if (c->armorClass > m1) - c->armorClass = m1; - } - - if (c->armorClass < -10) - c->armorClass = -10; -} - -int EoBCoreEngine::validateWeaponSlotItem(int index, int slot) { - EoBCharacter *c = &_characters[index]; - int itm1 = c->inventory[0]; - int r = itemUsableByCharacter(index, itm1); - int tp1 = _items[itm1].type; - - if (!slot) - return (!itm1 || r) ? 1 : 0; - - int itm2 = c->inventory[1]; - r = itemUsableByCharacter(index, itm2); - int tp2 = _items[itm2].type; - - if (itm1 && _itemTypes[tp1].requiredHands == 2) - return 0; - - if (!itm2) - return 1; - - int f = (_itemTypes[tp2].extraProperties & 0x7F); - if (f <= 0 || f > 3) - return r; - - if (_itemTypes[tp2].requiredHands) - return 0; - - return r; -} - -int EoBCoreEngine::getClericPaladinLevel(int index) { - if (_castScrollSlot) - return 9; - - if (index == -1) - return (_currentLevel < 7) ? 5 : 9; - - int l = getCharacterLevelIndex(2, _characters[index].cClass); - if (l > -1) - return _characters[index].level[l]; - - l = getCharacterLevelIndex(4, _characters[index].cClass); - if (l > -1) { - if (_characters[index].level[l] > 8) - return _characters[index].level[l] - 8; - } - - return 1; -} - -int EoBCoreEngine::getMageLevel(int index) { - if (_castScrollSlot) - return 9; - - if (index == -1) - return (_currentLevel < 7) ? 5 : 9; - - int l = getCharacterLevelIndex(1, _characters[index].cClass); - return (l > -1) ? _characters[index].level[l] : 1; -} - -int EoBCoreEngine::getCharacterLevelIndex(int type, int cClass) { - if (getCharacterClassType(cClass, 0) == type) - return 0; - - if (getCharacterClassType(cClass, 1) == type) - return 1; - - if (getCharacterClassType(cClass, 2) == type) - return 2; - - return -1; -} - -int EoBCoreEngine::countCharactersWithSpecificItems(int16 itemType, int16 itemValue) { - int res = 0; - for (int i = 0; i < 6; i++) { - if (!testCharacter(i, 1)) - continue; - if (checkInventoryForItem(i, itemType, itemValue) != -1) - res++; - } - return res; -} - -int EoBCoreEngine::checkInventoryForItem(int character, int16 itemType, int16 itemValue) { - if (character < 0) - return -1; - - for (int i = 0; i < 27; i++) { - uint16 inv = _characters[character].inventory[i]; - if (!inv) - continue; - if (_items[inv].type != itemType && itemType != -1) - continue; - if (_items[inv].value == itemValue || itemValue == -1) - return i; - } - return -1; -} - -void EoBCoreEngine::modifyCharacterHitpoints(int character, int16 points) { - if (!testCharacter(character, 3)) - return; - - EoBCharacter *c = &_characters[character]; - c->hitPointsCur += points; - if (c->hitPointsCur > c->hitPointsMax) - c->hitPointsCur = c->hitPointsMax; - - gui_drawHitpoints(character); - gui_drawCharPortraitWithStats(character); -} - -void EoBCoreEngine::neutralizePoison(int character) { - _characters[character].flags &= ~2; - _characters[character].effectFlags &= ~0x2000; - deleteCharEventTimer(character, -34); - gui_drawCharPortraitWithStats(character); -} - -void EoBCoreEngine::npcSequence(int npcIndex) { - _screen->loadShapeSetBitmap("OUTTAKE", 5, 3); - _screen->copyRegion(0, 0, 0, 0, 176, 120, 0, 6, Screen::CR_NO_P_CHECK); - - drawNpcScene(npcIndex); - - Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT"); - _screen->loadFileDataToPage(s, 5, 32000); - delete s; - - gui_drawBox(0, 121, 320, 79, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); - _txt->setupField(9, true); - _txt->resetPageBreakString(); - - runNpcDialogue(npcIndex); - - _txt->removePageBreakFlag(); - gui_restorePlayField(); -} - -void EoBCoreEngine::initNpc(int npcIndex) { - EoBCharacter *c = _characters; - int i = 0; - for (; i < 6; i++) { - if (!(_characters[i].flags & 1)) { - c = &_characters[i]; - break; - } - } - - delete[] c->faceShape; - memcpy(c, &_npcPreset[npcIndex], sizeof(EoBCharacter)); - recalcArmorClass(i); - - for (i = 0; i < 25; i++) { - if (!c->inventory[i]) - continue; - c->inventory[i] = duplicateItem(c->inventory[i]); - } - - _screen->loadShapeSetBitmap(_flags.gameID == GI_EOB2 ? "OUTPORTS" : "OUTTAKE", 3, 3); - _screen->_curPage = 2; - c->faceShape = _screen->encodeShape(npcIndex << 2, _flags.gameID == GI_EOB2 ? 0 : 160, 4, 32, true, _cgaMappingDefault); - _screen->_curPage = 0; -} - -int EoBCoreEngine::npcJoinDialogue(int npcIndex, int queryJoinTextId, int confirmJoinTextId, int noJoinTextId) { - gui_drawDialogueBox(); - _txt->printDialogueText(queryJoinTextId, 0); - - int r = runDialogue(-1, 2, _yesNoStrings[0], _yesNoStrings[1]) - 1; - if (r == 0) { - if (confirmJoinTextId == -1) { - Common::String tmp = Common::String::format(_npcJoinStrings[0], _npcPreset[npcIndex].name); - _txt->printDialogueText(tmp.c_str(), true); - } else { - _txt->printDialogueText(confirmJoinTextId, _okStrings[0]); - } - - if (prepareForNewPartyMember(33, npcIndex + 1)) - initNpc(npcIndex); - - } else if (r == 1) { - _txt->printDialogueText(noJoinTextId, _okStrings[0]); - } - - return r ^ 1; -} - -int EoBCoreEngine::prepareForNewPartyMember(int16 itemType, int16 itemValue) { - int numChars = 0; - for (int i = 0; i < 6; i++) - numChars += (_characters[i].flags & 1); - - if (numChars < 6) { - deletePartyItems(itemType, itemValue); - } else { - gui_drawDialogueBox(); - _screen->set16bitShadingLevel(4); - _txt->printDialogueText(_npcMaxStrings[0]); - _screen->set16bitShadingLevel(0); - int r = runDialogue(-1, 7, _characters[0].name, _characters[1].name, _characters[2].name, _characters[3].name, - _characters[4].name, _characters[5].name, _abortStrings[0]) - 1; - - if (r == 6) - return 0; - - deletePartyItems(itemType, itemValue); - removeCharacterFromParty(r); - } - - return 1; -} - -void EoBCoreEngine::dropCharacter(int charIndex) { - if (!testCharacter(charIndex, 1)) - return; - - removeCharacterFromParty(charIndex); - - if (charIndex < 5) - exchangeCharacters(charIndex, testCharacter(5, 1) ? 5 : 4); - - gui_processCharPortraitClick(0); - gui_setPlayFieldButtons(); - setupCharacterTimers(); -} - -void EoBCoreEngine::removeCharacterFromParty(int charIndex) { - EoBCharacter *c = &_characters[charIndex]; - c->flags = 0; - - for (int i = 0; i < 27; i++) { - if (i == 16 || !c->inventory[i]) - continue; - - setItemPosition((Item *)&_levelBlockProperties[_currentBlock & 0x3FF].drawObjects, _currentBlock, c->inventory[i], _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]); - c->inventory[i] = 0; - } - - while (c->inventory[16]) - setItemPosition((Item *)&_levelBlockProperties[_currentBlock & 0x3FF].drawObjects, _currentBlock, getQueuedItem(&c->inventory[16], 0, -1), _dropItemDirIndex[(_currentDirection << 2) + rollDice(1, 2, -1)]); - - c->inventory[16] = 0; - - if (_updateCharNum == charIndex) - _updateCharNum = 0; - - setupCharacterTimers(); -} - -void EoBCoreEngine::exchangeCharacters(int charIndex1, int charIndex2) { - EoBCharacter temp; - memcpy(&temp, &_characters[charIndex1], sizeof(EoBCharacter)); - memcpy(&_characters[charIndex1], &_characters[charIndex2], sizeof(EoBCharacter)); - memcpy(&_characters[charIndex2], &temp, sizeof(EoBCharacter)); -} - -void EoBCoreEngine::increasePartyExperience(int16 points) { - int cnt = 0; - for (int i = 0; i < 6; i++) { - if (testCharacter(i, 3)) - cnt++; - } - - if (cnt <= 0) - return; - - points /= cnt; - - for (int i = 0; i < 6; i++) { - if (!testCharacter(i, 3)) - continue; - increaseCharacterExperience(i, points); - } -} - -void EoBCoreEngine::increaseCharacterExperience(int charIndex, int32 points) { - int cl = _characters[charIndex].cClass; - points /= _numLevelsPerClass[cl]; - - for (int i = 0; i < 3; i++) { - if (getCharacterClassType(cl, i) == -1) - continue; - _characters[charIndex].experience[i] += points; - - uint32 er = getRequiredExperience(cl, i, _characters[charIndex].level[i] + 1); - if (er == 0xFFFFFFFF) - continue; - - if (_characters[charIndex].experience[i] >= er) - increaseCharacterLevel(charIndex, i); - } -} - -uint32 EoBCoreEngine::getRequiredExperience(int cClass, int levelIndex, int level) { - cClass = getCharacterClassType(cClass, levelIndex); - if (cClass == -1) - return 0xFFFFFFFF; - - const uint32 *tbl = _expRequirementTables[cClass]; - return tbl[level - 1]; -} - -void EoBCoreEngine::increaseCharacterLevel(int charIndex, int levelIndex) { - _characters[charIndex].level[levelIndex]++; - int hpInc = generateCharacterHitpointsByLevel(charIndex, 1 << levelIndex); - _characters[charIndex].hitPointsCur += hpInc; - _characters[charIndex].hitPointsMax += hpInc; - - gui_drawCharPortraitWithStats(charIndex); - _txt->printMessage(_levelGainStrings[0], -1, _characters[charIndex].name); - snd_playSoundEffect(23); -} - -void EoBCoreEngine::setWeaponSlotStatus(int charIndex, int mode, int slot) { - if (mode == 0 || mode == 2) - _characters[charIndex].disabledSlots ^= (1 << slot); - else if (mode != 1) - return; - - _characters[charIndex].slotStatus[slot] = 0; - gui_drawCharPortraitWithStats(charIndex); -} - -void EoBCoreEngine::setupDialogueButtons(int presetfirst, int numStr, va_list &args) { - _dialogueNumButtons = numStr; - _dialogueHighlightedButton = 0; - - Screen::FontId of = _screen->setFont((_flags.gameID == GI_EOB2 && _flags.platform == Common::kPlatformFMTowns) ? Screen::FID_8_FNT : _screen->_currentFont); - - for (int i = 0; i < numStr; i++) { - const char *s = va_arg(args, const char *); - if (s) - _dialogueButtonString[i] = s; - else - _dialogueNumButtons = numStr = i; - } - - static const uint16 prsX[] = { 59, 166, 4, 112, 220, 4, 112, 220, 4, 112, 220, 4, 112, 220 }; - static const uint8 prsY[] = { 0, 0, 0, 0, 0, 12, 12, 12, 24, 24, 24, 36, 36, 36 }; - - const ScreenDim *dm = screen()->_curDim; - int yOffs = (_txt->lineCount() + 1) * _screen->getFontHeight() + dm->sy + 4; - - _dialogueButtonPosX = &prsX[presetfirst]; - _dialogueButtonPosY = &prsY[presetfirst]; - _dialogueButtonYoffs = yOffs; - - drawDialogueButtons(); - - _screen->setFont(of); - - if (!shouldQuit()) - removeInputTop(); -} - -void EoBCoreEngine::initDialogueSequence() { - _npcSequenceSub = -1; - _txt->setWaitButtonMode(0); - _dialogueField = true; - - _dialogueLastBitmap[0] = 0; - - _txt->resetPageBreakString(); - gui_updateControls(); - //_allowSkip = true; - - // WORKAROUND for bug in the original code (all platforms). Sequence sound would be terminated prematurely. - if (_flags.gameID == GI_EOB2 && _currentLevel == 2 && _currentBlock == 654) - _sound->stopAllSoundEffects(); - else - snd_stopSound(); - - Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT"); - _screen->loadFileDataToPage(s, 5, 32000); - _txt->setupField(9, 0); - delete s; -} - -void EoBCoreEngine::restoreAfterDialogueSequence() { - _txt->allowPageBreak(false); - _dialogueField = false; - - _dialogueLastBitmap[0] = 0; - - gui_restorePlayField(); - //_allowSkip = false; - _screen->setScreenDim(7); - - if (_flags.gameID == GI_EOB2) - snd_playSoundEffect(2); - - _sceneUpdateRequired = true; -} - -void EoBCoreEngine::drawSequenceBitmap(const char *file, int destRect, int x1, int y1, int flags) { - static const uint8 frameX[] = { 1, 0 }; - static const uint8 frameY[] = { 8, 0 }; - static const uint8 frameW[] = { 20, 40 }; - static const uint8 frameH[] = { 96, 121 }; - - int page = ((flags & 2) || destRect) ? 0 : 6; - - if (scumm_stricmp(_dialogueLastBitmap, file)) { - _screen->clearPage(2); - if (!destRect) { - if (!(flags & 1)) { - _screen->loadEoBBitmap("BORDER", 0, 3, 3, 2); - _screen->copyRegion(0, 0, 0, 0, 184, 121, 2, page, Screen::CR_NO_P_CHECK); - } else { - _screen->copyRegion(0, 0, 0, 0, 184, 121, 0, page, Screen::CR_NO_P_CHECK); - } - - if (!page) - _screen->copyRegion(0, 0, 0, 0, 184, 121, 2, 6, Screen::CR_NO_P_CHECK); - } - - _screen->loadEoBBitmap(file, 0, 3, 3, 2); - strcpy(_dialogueLastBitmap, file); - } - - if (flags & 2) - _screen->crossFadeRegion(x1 << 3, y1, frameX[destRect] << 3, frameY[destRect], frameW[destRect] << 3, frameH[destRect], 2, page); - else - _screen->copyRegion(x1 << 3, y1, frameX[destRect] << 3, frameY[destRect], frameW[destRect] << 3, frameH[destRect], 2, page, Screen::CR_NO_P_CHECK); - - if (page == 6) - _screen->copyRegion(0, 0, 0, 0, 184, 121, 6, 0, Screen::CR_NO_P_CHECK); - - _screen->updateScreen(); -} - -int EoBCoreEngine::runDialogue(int dialogueTextId, int numStr, ...) { - if (dialogueTextId != -1) - txt()->printDialogueText(dialogueTextId, 0); - - va_list args; - va_start(args, numStr); - if (numStr > 2) - setupDialogueButtons(2, numStr, args); - else - setupDialogueButtons(0, numStr, args); - va_end(args); - - int res = 0; - while (res == 0 && !shouldQuit()) - res = processDialogue(); - - gui_drawDialogueBox(); - - return res; -} - -void EoBCoreEngine::restParty_displayWarning(const char *str) { - int od = _screen->curDimIndex(); - _screen->setScreenDim(7); - Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); - _screen->setCurPage(0); - - _txt->printMessage(Common::String::format("\r%s\r", str).c_str()); - - _screen->setFont(of); - _screen->setScreenDim(od); -} - -bool EoBCoreEngine::restParty_updateMonsters() { - bool sfxEnabled = _sound->sfxEnabled(); - bool musicEnabled = _sound->musicEnabled(); - _sound->enableSFX(false); - _sound->enableMusic(false); - - for (int i = 0; i < 5; i++) { - _partyResting = true; - Screen::FontId of = _screen->setFont(Screen::FID_6_FNT); - int od = _screen->curDimIndex(); - _screen->setScreenDim(7); - updateMonsters(0); - updateMonsters(1); - timerProcessFlyingObjects(0); - _screen->setScreenDim(od); - _screen->setFont(of); - _partyResting = false; - - for (int ii = 0; ii < 30; ii++) { - if (_monsters[ii].mode == 8) - continue; - if (getBlockDistance(_currentBlock, _monsters[ii].block) >= 2) - continue; - - restParty_displayWarning(_menuStringsRest4[0]); - _sound->enableSFX(sfxEnabled); - _sound->enableMusic(musicEnabled); - return true; - } - } - - _sound->enableSFX(sfxEnabled); - _sound->enableMusic(musicEnabled); - return false; -} - -int EoBCoreEngine::restParty_getCharacterWithLowestHp() { - int lhp = 900; - int res = -1; - - for (int i = 0; i < 6; i++) { - if (!testCharacter(i, 3)) - continue; - if (_characters[i].hitPointsCur >= _characters[i].hitPointsMax) - continue; - if (_characters[i].hitPointsCur < lhp) { - lhp = _characters[i].hitPointsCur; - res = i; - } - } - - return res + 1; -} - -bool EoBCoreEngine::restParty_checkHealSpells(int charIndex) { - static const uint8 eob1healSpells[] = { 2, 15, 20 }; - static const uint8 eob2healSpells[] = { 3, 16, 20 }; - const uint8 *spells = _flags.gameID == GI_EOB1 ? eob1healSpells : eob2healSpells; - const int8 *list = _characters[charIndex].clericSpells; - - for (int i = 0; i < 80; i++) { - int s = list[i] < 0 ? -list[i] : list[i]; - if (s == spells[0] || s == spells[1] || s == spells[2]) - return true; - } - - return false; -} - -bool EoBCoreEngine::restParty_checkSpellsToLearn() { - for (int i = 0; i < 6; i++) { - if (!testCharacter(i, 0x43)) - continue; - - if ((getCharacterLevelIndex(2, _characters[i].cClass) != -1 || getCharacterLevelIndex(4, _characters[i].cClass) != -1) && (checkInventoryForItem(i, 30, -1) != -1)) { - for (int ii = 0; ii < 80; ii++) { - if (_characters[i].clericSpells[ii] < 0) - return true; - } - } - - if ((getCharacterLevelIndex(1, _characters[i].cClass) != -1) && (checkInventoryForItem(i, 29, -1) != -1)) { - for (int ii = 0; ii < 80; ii++) { - if (_characters[i].mageSpells[ii] < 0) - return true; - } - } - } - - return false; -} - -bool EoBCoreEngine::restParty_extraAbortCondition() { - return false; -} - -void EoBCoreEngine::delay(uint32 millis, bool, bool) { - while (millis && !shouldQuit() && !(_allowSkip && skipFlag())) { - updateInput(); - uint32 step = MIN(millis, (_tickLength / 5)); - _system->delayMillis(step); - millis -= step; - } -} - -void EoBCoreEngine::displayParchment(int id) { - _txt->setWaitButtonMode(1); - _txt->resetPageBreakString(); - gui_updateControls(); - - if (id >= 0) { - // display text - Common::SeekableReadStream *s = _res->createReadStream("TEXT.DAT"); - _screen->loadFileDataToPage(s, 5, 32000); - _screen->set16bitShadingLevel(4); - gui_drawBox(0, 0, 176, 175, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); - _screen->set16bitShadingLevel(0); - _txt->setupField(12, 1); - if (_flags.gameID == GI_EOB2) - id++; - _txt->printDialogueText(id, _okStrings[0]); - - } else { - // display bitmap - id = -id - 1; - static const uint8 x[] = { 0, 20, 0 }; - static const uint8 y[] = { 0, 0, 96 }; - drawSequenceBitmap("MAP", 0, x[id], y[id], 0); - - removeInputTop(); - while (!shouldQuit()) { - delay(_tickLength); - if (checkInput(0, false, 0) & 0xFF) - break; - removeInputTop(); - } - removeInputTop(); - } - - restoreAfterDialogueSequence(); -} - -int EoBCoreEngine::countResurrectionCandidates() { - _rrCount = 0; - memset(_rrNames, 0, 10 * sizeof(const char *)); - - for (int i = 0; i < 6; i++) { - if (!testCharacter(i, 1)) - continue; - if (_characters[i].hitPointsCur != -10) - continue; - - _rrNames[_rrCount] = _characters[i].name; - _rrId[_rrCount++] = i; - } - - for (int i = 0; i < 6; i++) { - if (!testCharacter(i, 1)) - continue; - - for (int ii = 0; ii < 27; ii++) { - uint16 inv = _characters[i].inventory[ii]; - if (!inv) - continue; - - if ((_flags.gameID == GI_EOB1 && ((_itemTypes[_items[inv].type].extraProperties & 0x7F) != 8)) || (_flags.gameID == GI_EOB2 && _items[inv].type != 33)) - continue; - - _rrNames[_rrCount] = _npcPreset[_items[inv].value - 1].name; - _rrId[_rrCount++] = -_items[inv].value; - } - } - - if (_itemInHand > 0) { - if ((_flags.gameID == GI_EOB1 && ((_itemTypes[_items[_itemInHand].type].extraProperties & 0x7F) == 8)) || (_flags.gameID == GI_EOB2 && _items[_itemInHand].type == 33)) { - _rrNames[_rrCount] = _npcPreset[_items[_itemInHand].value - 1].name; - _rrId[_rrCount++] = -_items[_itemInHand].value; - } - } - - return _rrCount; -} - -void EoBCoreEngine::seq_portal() { - uint8 *shapes1[5]; - uint8 *shapes2[5]; - uint8 *shapes3[5]; - uint8 *shape0; - - _screen->loadShapeSetBitmap("PORTALA", 5, 3); - - for (int i = 0; i < 5; i++) { - shapes1[i] = _screen->encodeShape(i * 3, 0, 3, 75, false, _cgaMappingDefault); - shapes2[i] = _screen->encodeShape(i * 3, 80, 3, 75, false, _cgaMappingDefault); - shapes3[i] = _screen->encodeShape(15, i * 18, 15, 18, false, _cgaMappingDefault); - } - - shape0 = _screen->encodeShape(30, 0, 8, 77, false, _cgaMappingDefault); - _screen->loadEoBBitmap("PORTALB", _cgaMappingDefault, 5, 3, 2); - - snd_playSoundEffect(33); - snd_playSoundEffect(19); - _screen->copyRegion(24, 0, 24, 0, 144, 104, 2, 5, Screen::CR_NO_P_CHECK); - _screen->copyRegion(24, 0, 24, 0, 144, 104, 0, 2, Screen::CR_NO_P_CHECK); - _screen->drawShape(2, shapes3[0], 28, 9, 0); - _screen->drawShape(2, shapes1[0], 34, 28, 0); - _screen->drawShape(2, shapes2[0], 120, 28, 0); - _screen->drawShape(2, shape0, 56, 27, 0); - _screen->crossFadeRegion(24, 0, 24, 0, 144, 104, 2, 0); - _screen->copyRegion(24, 0, 24, 0, 144, 104, 5, 2, Screen::CR_NO_P_CHECK); - delay(30 * _tickLength); - - for (const int8 *pos = _portalSeq; *pos > -1 && !shouldQuit();) { - int s = *pos++; - _screen->drawShape(0, shapes3[s], 28, 9, 0); - _screen->drawShape(0, shapes1[s], 34, 28, 0); - _screen->drawShape(0, shapes2[s], 120, 28, 0); - - if ((s == 1) && (pos >= _portalSeq + 3)) { - if (*(pos - 3) == 0) { - snd_playSoundEffect(24); - snd_playSoundEffect(86); - } - } - - s = *pos++; - if (s == 0) { - _screen->drawShape(0, shape0, 56, 27, 0); - } else { - s--; - _screen->copyRegion((s % 5) << 6, s / 5 * 77, 56, 27, 64, 77, 2, 0, Screen::CR_NO_P_CHECK); - } - - if (s == 1) - snd_playSoundEffect(31); - else if (s == 3) { - if (*(pos - 2) == 3) - snd_playSoundEffect(90); - } - - _screen->updateScreen(); - delay(2 * _tickLength); - } - - delete[] shape0; - for (int i = 0; i < 5; i++) { - delete[] shapes1[i]; - delete[] shapes2[i]; - delete[] shapes3[i]; - } -} - -bool EoBCoreEngine::checkPassword() { - char answ[20]; - Screen::FontId of = _screen->setFont(Screen::FID_8_FNT); - _screen->copyPage(0, 10); - - _screen->setScreenDim(13); - gui_drawBox(_screen->_curDim->sx << 3, _screen->_curDim->sy, _screen->_curDim->w << 3, _screen->_curDim->h, guiSettings()->colors.frame1, guiSettings()->colors.frame2, -1); - gui_drawBox((_screen->_curDim->sx << 3) + 1, _screen->_curDim->sy + 1, (_screen->_curDim->w << 3) - 2, _screen->_curDim->h - 2, guiSettings()->colors.frame1, guiSettings()->colors.frame2, guiSettings()->colors.fill); - _screen->modifyScreenDim(13, _screen->_curDim->sx + 1, _screen->_curDim->sy + 2, _screen->_curDim->w - 2, _screen->_curDim->h - 16); - - for (int i = 0; i < 3; i++) { - _screen->fillRect(_screen->_curDim->sx << 3, _screen->_curDim->sy, ((_screen->_curDim->sx + _screen->_curDim->w) << 3) - 1, (_screen->_curDim->sy + _screen->_curDim->h) - 1, guiSettings()->colors.fill); - int c = rollDice(1, _mnNumWord - 1, -1); - const uint8 *shp = (_mnDef[c << 2] < _numLargeItemShapes) ? _largeItemShapes[_mnDef[c << 2]] : (_mnDef[c << 2] < 15 ? 0 : _smallItemShapes[_mnDef[c << 2] - 15]); - assert(shp); - _screen->drawShape(0, shp, 100, 2, 13); - _screen->printShadedText(Common::String::format(_mnPrompt[0], _mnDef[(c << 2) + 1], _mnDef[(c << 2) + 2]).c_str(), (_screen->_curDim->sx + 1) << 3, _screen->_curDim->sy, _screen->_curDim->unk8, guiSettings()->colors.fill); - memset(answ, 0, 20); - gui_drawBox(76, 100, 133, 14, guiSettings()->colors.frame2, guiSettings()->colors.frame1, -1); - gui_drawBox(77, 101, 131, 12, guiSettings()->colors.frame2, guiSettings()->colors.frame1, -1); - if (_gui->getTextInput(answ, 10, 103, 15, _screen->_curDim->unk8, guiSettings()->colors.fill, 8) < 0) - i = 3; - if (!scumm_stricmp(_mnWord[c], answ)) - break; - else if (i == 2) - return false; - } - - _screen->modifyScreenDim(13, _screen->_curDim->sx - 1, _screen->_curDim->sy - 2, _screen->_curDim->w + 2, _screen->_curDim->h + 16); - _screen->setFont(of); - _screen->copyPage(10, 0); - return true; -} - -Common::String EoBCoreEngine::convertAsciiToSjis(Common::String str) { - if (_flags.platform != Common::kPlatformFMTowns) - return str; - - Common::String n; - const char *src = str.c_str(); - int pos = 0; - for (uint32 i = 0; i < str.size(); ++i) { - if (src[i] & 0x80) { - n.insertChar(src[i++], pos++); - n.insertChar(src[i], pos++); - } else if (src[i] >= 32 && src[i] <= 64) { - n.insertChar(_ascii2SjisTables[1][(src[i] - 32) * 2], pos++); - n.insertChar(_ascii2SjisTables[1][(src[i] - 32) * 2 + 1], pos++); - } else if ((src[i] >= 97 && src[i] <= 122) || (src[i] >= 65 && src[i] <= 90)) { - char c = (src[i] >= 97) ? src[i] - 97 : src[i] - 65; - n.insertChar(_ascii2SjisTables2[0][c * 2], pos++); - n.insertChar(_ascii2SjisTables2[0][c * 2 + 1], pos++); - } - } - - return n; -} - -void EoBCoreEngine::useSlotWeapon(int charIndex, int slotIndex, Item item) { - EoBCharacter *c = &_characters[charIndex]; - int tp = item ? _items[item].type : 0; - - if (c->effectFlags & 0x40) - removeCharacterEffect(10, charIndex, 1); // remove invisibility effect - - int ep = _itemTypes[tp].extraProperties & 0x7F; - int8 inflict = 0; - - if (ep == 1) { - inflict = closeDistanceAttack(charIndex, item); - if (!inflict) - inflict = -1; - snd_playSoundEffect(32); - } else if (ep == 2) { - inflict = thrownAttack(charIndex, slotIndex, item); - } else if (ep == 3) { - inflict = projectileWeaponAttack(charIndex, item); - gui_drawCharPortraitWithStats(charIndex); - } - - if (inflict > 0) { - if (_items[item].flags & 8) { - c->hitPointsCur += inflict; - gui_drawCharPortraitWithStats(charIndex); - } - - if (_items[item].flags & 0x10) - c->inventory[slotIndex] = 0; - - inflictMonsterDamage(&_monsters[_dstMonsterIndex], inflict, true); - } - - c->disabledSlots ^= (1 << slotIndex); - c->slotStatus[slotIndex] = inflict; - - gui_drawCharPortraitWithStats(charIndex); - setCharEventTimer(charIndex, 18, inflict >= -2 ? slotIndex + 2 : slotIndex, 1); -} - -int EoBCoreEngine::closeDistanceAttack(int charIndex, Item item) { - if (charIndex > 1) - return -3; - - uint16 d = calcNewBlockPosition(_currentBlock, _currentDirection); - int r = getClosestMonster(charIndex, d); - - if (r == -1) { - uint8 w = _specialWallTypes[_levelBlockProperties[d].walls[_sceneDrawVarDown]]; - if (w == 0xFF) { - if (_flags.gameID == GI_EOB1) { - _levelBlockProperties[d].walls[_sceneDrawVarDown]++; - _levelBlockProperties[d].walls[_sceneDrawVarDown ^ 2]++; - - } else { - for (int i = 0; i < 4; i++) { - if (_specialWallTypes[_levelBlockProperties[d].walls[i]] == 0xFF) - _levelBlockProperties[d].walls[i]++; - } - } - _sceneUpdateRequired = true; - - } else if ((_flags.gameID == GI_EOB1) || (_flags.gameID == GI_EOB2 && w != 8 && w != 9)) { - return -1; - } - - return (_flags.gameID == GI_EOB2 && ((_itemTypes[_items[item].type].allowedClasses & 4) || !item)) ? -5 : -2; - - } else { - if (_monsters[r].flags & 0x20) { - killMonster(&_monsters[r], 1); - _txt->printMessage(_monsterDustStrings[0]); - return -2; - } - - if (!characterAttackHitTest(charIndex, r, item, 1)) - return -1; - - uint16 flg = 0x100; - - if (isMagicEffectItem(item)) - flg |= 1; - - _dstMonsterIndex = r; - return calcMonsterDamage(&_monsters[r], charIndex, item, 1, flg, 5, 3); - } - - return 0; -} - -int EoBCoreEngine::thrownAttack(int charIndex, int slotIndex, Item item) { - int d = charIndex > 3 ? charIndex - 2 : charIndex; - if (!launchObject(charIndex, item, _currentBlock, _dropItemDirIndex[(_currentDirection << 2) + d], _currentDirection, _items[item].type)) - return 0; - - snd_playSoundEffect(11); - _characters[charIndex].inventory[slotIndex] = 0; - reloadWeaponSlot(charIndex, slotIndex, -1, 0); - _sceneUpdateRequired = true; - return 0; -} - -int EoBCoreEngine::projectileWeaponAttack(int charIndex, Item item) { - int tp = _items[item].type; - - if (_flags.gameID == GI_EOB1) - assert(tp >= 7); - - int t = _projectileWeaponAmmoTypes[_flags.gameID == GI_EOB1 ? tp - 7 : tp]; - Item ammoItem = 0; - - if (t == 16) { - if (_characters[charIndex].inventory[0] && _items[_characters[charIndex].inventory[0]].type == 16) - SWAP(ammoItem, _characters[charIndex].inventory[0]); - else if (_characters[charIndex].inventory[1] && _items[_characters[charIndex].inventory[1]].type == 16) - SWAP(ammoItem, _characters[charIndex].inventory[1]); - else if (_characters[charIndex].inventory[16]) - ammoItem = getQueuedItem(&_characters[charIndex].inventory[16], 0, -1); - - } else { - for (int i = 0; i < 27; i++) { - if (_items[_characters[charIndex].inventory[i]].type == t) { - SWAP(ammoItem, _characters[charIndex].inventory[i]); - if (i < 2) - gui_drawCharPortraitWithStats(charIndex); - break; - } - } - } - - if (!ammoItem) - return -4; - - int c = charIndex; - if (c > 3) - c -= 2; - - if (launchObject(charIndex, ammoItem, _currentBlock, _dropItemDirIndex[(_currentDirection << 2) + c], _currentDirection, tp)) { - snd_playSoundEffect(tp == 7 ? 26 : 11); - _sceneUpdateRequired = true; - } - - return 0; -} - -void EoBCoreEngine::inflictMonsterDamage(EoBMonsterInPlay *m, int damage, bool giveExperience) { - m->hitPointsCur -= damage; - m->flags = (m->flags & 0xF7) | 1; - - if (_monsterProps[m->type].capsFlags & 0x2000) { - explodeMonster(m); - checkSceneUpdateNeed(m->block); - m->hitPointsCur = 0; - } else { - if (checkSceneUpdateNeed(m->block)) { - m->flags |= 2; - if (_preventMonsterFlash) - return; - flashMonsterShape(m); - } - } - - if (m->hitPointsCur <= 0) - killMonster(m, giveExperience); - else if (getBlockDistance(m->block, _currentBlock) < 4) - m->dest = _currentBlock; -} - -void EoBCoreEngine::calcAndInflictMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect) { - int dmg = calcMonsterDamage(m, times, pips, offs, flags, savingThrowType, savingThrowEffect); - if (dmg > 0) - inflictMonsterDamage(m, dmg, flags & 0x800 ? true : false); -} - -void EoBCoreEngine::calcAndInflictCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int savingThrowEffect) { - int dmg = calcCharacterDamage(charIndex, times, itemOrPips, useStrModifierOrBase, flags, savingThrowType, savingThrowEffect); - if (dmg) - inflictCharacterDamage(charIndex, dmg); -} - -int EoBCoreEngine::calcCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int savingThrowEffect) { - int s = (flags & 0x100) ? calcDamageModifers(times, 0, itemOrPips, _items[itemOrPips].type, useStrModifierOrBase) : rollDice(times, itemOrPips, useStrModifierOrBase); - EoBCharacter *c = &_characters[charIndex]; - - if (savingThrowType != 5) { - if (trySavingThrow(c, _charClassModifier[c->cClass], c->level[0], savingThrowType, c->raceSex >> 1 /*fix bug in original code by adding a right shift*/)) - s = savingThrowReduceDamage(savingThrowEffect, s); - } - - if ((flags & 0x110) == 0x110) { - if (!calcDamageCheckItemType(_items[itemOrPips].type)) - s = 1; - } - - if (flags & 4) { - if (checkInventoryForRings(charIndex, 3)) - s = 0; - } - - if (flags & 0x400) { - if (c->effectFlags & 0x2000) - s = 0; - else - _txt->printMessage(_characterStatusStrings8[0], -1, c->name); - } - - return s; -} - -void EoBCoreEngine::inflictCharacterDamage(int charIndex, int damage) { - EoBCharacter *c = &_characters[charIndex]; - if (!testCharacter(charIndex, 3)) - return; - - if (c->effectsRemainder[3]) - c->effectsRemainder[3] = (damage < c->effectsRemainder[3]) ? (c->effectsRemainder[3] - damage) : 0; - - c->hitPointsCur -= damage; - c->damageTaken = damage; - - if (c->hitPointsCur > -10) { - snd_playSoundEffect(21); - } else { - c->hitPointsCur = -10; - c->flags &= 1; - c->food = 0; - removeAllCharacterEffects(charIndex); - snd_playSoundEffect(22); - } - - if (c->effectsRemainder[0]) { - c->effectsRemainder[0] = (damage < c->effectsRemainder[0]) ? (c->effectsRemainder[0] - damage) : 0; - if (!c->effectsRemainder[0]) - removeCharacterEffect(1, charIndex, 1); - } - - if (_currentControlMode) - gui_drawFaceShape(charIndex); - else - gui_drawCharPortraitWithStats(charIndex); - - if (c->hitPointsCur <= 0 && _updateFlags == 1 && charIndex == _openBookChar) { - Button b; - clickedSpellbookAbort(&b); - } - - setCharEventTimer(charIndex, 18, 6, 1); -} - -bool EoBCoreEngine::characterAttackHitTest(int charIndex, int monsterIndex, int item, int attackType) { - if (charIndex < 0) - return true; - - int p = item ? (_flags.gameID == GI_EOB1 ? _items[item].type : (_itemTypes[_items[item].type].extraProperties & 0x7F)) : 0; - - if (_monsters[monsterIndex].flags & 0x20) - return true;// EOB 2 only ? - - int t = _monsters[monsterIndex].type; - int d = (p < 1 || p > 3) ? 0 : _items[item].value; - - if (_flags.gameID == GI_EOB2) { - if ((p > 0 && p < 4) || !item) { - if (((_monsterProps[t].immunityFlags & 0x200) && (d <= 0)) || ((_monsterProps[t].immunityFlags & 0x1000) && (d <= 1))) - return false; - } - } - - d += (attackType ? getStrHitChanceModifier(charIndex) : getDexHitChanceModifier(charIndex)); - - int m = getMonsterAcHitChanceModifier(charIndex, _monsterProps[t].armorClass) - d; - int s = rollDice(1, 20); - - _monsters[monsterIndex].flags |= 1; - - if (_flags.gameID == GI_EOB1) { - if (_partyEffectFlags & 0x30) - s++; - if (_characters[charIndex].effectFlags & 0x40) - s++; - } else if ((_partyEffectFlags & 0x8400) || (_characters[charIndex].effectFlags & 0x1000)) { - s++; - } - - s = CLIP(s, 1, 20); - - return s >= m; -} - -bool EoBCoreEngine::monsterAttackHitTest(EoBMonsterInPlay *m, int charIndex) { - int tp = m->type; - EoBMonsterProperty *p = &_monsterProps[tp]; - - int r = rollDice(1, 20); - if (r != 20) { - // Prot from evil - if (_characters[charIndex].effectFlags & 0x800) - r -= 2; - // blur - if (_characters[charIndex].effectFlags & 0x10) - r -= 2; - // prayer - if (_partyEffectFlags & 0x8000) - r--; - } - - return ((r == 20) || (r >= (p->hitChance - _characters[charIndex].armorClass))); -} - -bool EoBCoreEngine::flyingObjectMonsterHit(EoBFlyingObject *fo, int monsterIndex) { - if (fo->attackerId != -1) { - if (!characterAttackHitTest(fo->attackerId, monsterIndex, fo->item, 0)) - return false; - } - calcAndInflictMonsterDamage(&_monsters[monsterIndex], fo->attackerId, fo->item, 0, (fo->attackerId == -1) ? 0x110 : 0x910, 5, 3); - return true; -} - -bool EoBCoreEngine::flyingObjectPartyHit(EoBFlyingObject *fo) { - int ps = _dscItemPosIndex[(_currentDirection << 2) + (_items[fo->item].pos & 3)]; - bool res = false; - - bool b = ((_currentDirection == fo->direction || _currentDirection == (fo->direction ^ 2)) && ps > 2); - int s = ps << 1; - if (ps > 2) - s += rollDice(1, 2, -1); - - static const int8 charId[] = { 0, -1, 1, -1, 2, 4, 3, 5 }; - - for (int i = 0; i < 2; i++) { - int c = charId[s]; - s ^= 1; - if (!testCharacter(c, 3)) - continue; - calcAndInflictCharacterDamage(c, -1, fo->item, 0, 0x110, 5, 3); - res = true; - if (ps < 2 || b == 0) - break; - } - - return res; -} - -void EoBCoreEngine::monsterCloseAttack(EoBMonsterInPlay *m) { - int first = _monsterCloseAttDstTable1[(_currentDirection << 2) + m->dir] * 12; - int v = (m->pos == 4) ? rollDice(1, 2, -1) : _monsterCloseAttChkTable2[(m->dir << 2) + m->pos]; - if (!v) - first += 6; - - int last = first + 6; - for (int i = first; i < last; i++) { - int c = _monsterCloseAttDstTable2[i]; - if (!testCharacter(c, 3)) - continue; - - // Character Invisibility - if ((_characters[c].effectFlags & 0x140) && (rollDice(1, 20) >= 5)) - continue; - - int dmg = 0; - for (int ii = 0; ii < _monsterProps[m->type].attacksPerRound; ii++) { - if (!monsterAttackHitTest(m, c)) - continue; - dmg += rollDice(_monsterProps[m->type].dmgDc[ii].times, _monsterProps[m->type].dmgDc[ii].pips, _monsterProps[m->type].dmgDc[ii].base); - if (_characters[c].effectsRemainder[1]) { - if (--_characters[c].effectsRemainder[1]) - dmg = 0; - } - } - - if (dmg > 0) { - if ((_monsterProps[m->type].capsFlags & 0x80) && rollDice(1, 4, -1) != 3) { - int slot = rollDice(1, 27, -1); - for (int iii = 0; iii < 27; iii++) { - Item itm = _characters[c].inventory[slot]; - if (!itm || !(_itemTypes[_items[itm].type].extraProperties & 0x80)) { - if (++slot == 27) - slot = 0; - continue; - } - - _characters[c].inventory[slot] = 0; - _txt->printMessage(_ripItemStrings[(_characters[c].raceSex & 1) ^ 1], -1, _characters[c].name); - printFullItemName(itm); - _txt->printMessage(_ripItemStrings[2]); - break; - } - gui_drawCharPortraitWithStats(c); - } - - inflictCharacterDamage(c, dmg); - - if (_monsterProps[m->type].capsFlags & 0x10) { - statusAttack(c, 2, _monsterSpecAttStrings[_flags.gameID == GI_EOB1 ? 3 : 2], 0, 1, 8, 1); - _characters[c].effectFlags &= ~0x2000; - } - - if (_monsterProps[m->type].capsFlags & 0x20) - statusAttack(c, 4, _monsterSpecAttStrings[_flags.gameID == GI_EOB1 ? 4 : 3], 2, 5, 9, 1); - - if (_monsterProps[m->type].capsFlags & 0x8000) - statusAttack(c, 8, _monsterSpecAttStrings[4], 2, 0, 0, 1); - - } - - if (!(_monsterProps[m->type].capsFlags & 0x4000)) - return; - } -} - -void EoBCoreEngine::monsterSpellCast(EoBMonsterInPlay *m, int type) { - launchMagicObject(-1, type, m->block, m->pos, m->dir); - snd_processEnvironmentalSoundEffect(_spells[_magicFlightObjectProperties[type << 2]].sound, m->block); -} - -void EoBCoreEngine::statusAttack(int charIndex, int attackStatusFlags, const char *attackStatusString, int savingThrowType, uint32 effectDuration, int restoreEvent, int noRefresh) { - EoBCharacter *c = &_characters[charIndex]; - if ((c->flags & attackStatusFlags) && noRefresh) - return; - if (!testCharacter(charIndex, 3)) - return; - - if (savingThrowType != 5 && specialAttackSavingThrow(charIndex, savingThrowType)) - return; - - if (attackStatusFlags & 8) { - removeAllCharacterEffects(charIndex); - c->flags = (c->flags & 1) | 8; - } else { - c->flags |= attackStatusFlags; - } - - if ((attackStatusFlags & 0x0C) && (_openBookChar == charIndex) && _updateFlags) { - Button b; - clickedSpellbookAbort(&b); - } - - if (effectDuration) - setCharEventTimer(charIndex, effectDuration * 546, restoreEvent, 1); - - gui_drawCharPortraitWithStats(charIndex); - _txt->printMessage(_characterStatusStrings13[0], -1, c->name, attackStatusString); -} - -int EoBCoreEngine::calcMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect) { - int s = flags & 0x100 ? calcDamageModifers(times, m, pips, _items[pips].type, offs) : rollDice(times, pips, offs); - EoBMonsterProperty *p = &_monsterProps[m->type]; - - if (savingThrowType != 5) { - if (trySavingThrow(m, 0, p->level, savingThrowType, 6)) - s = savingThrowReduceDamage(savingThrowEffect, s); - } - - if ((flags & 0x110) == 0x110) { - if (!calcDamageCheckItemType(_items[pips].type)) - s = 1; - } - - if ((flags & 0x100) && (!(_itemTypes[_items[pips].type].allowedClasses & 4 /* bug in original code ??*/)) - && ((_flags.gameID == GI_EOB2 && (p->immunityFlags & 0x100)) || (_flags.gameID == GI_EOB1 && (p->capsFlags & 4)))) - s >>= 1; - - if (p->immunityFlags & 0x2000) { - if (flags & 0x100) { - if (_items[pips].value < 3) - s >>= 2; - if (_items[pips].value == 3) - s >>= 1; - if (s == 0) - s = _items[pips].value; - - } else { - s >>= 1; - } - } - - if (flags & 1) { - if (tryMonsterAttackEvasion(m)) - s = 0; - } - - if (_flags.gameID == GI_EOB1) - return s; - - static const uint16 damageImmunityFlags[] = { 0x01, 0x10, 0x02, 0x20, 0x80, 0x400, 0x20, 0x800, 0x40, 0x80, 0x400, 0x40 }; - for (int i = 0; i < 12; i += 2) { - if ((flags & damageImmunityFlags[i]) && (p->immunityFlags & damageImmunityFlags[i + 1])) - s = 0; - } - - return s; -} - -int EoBCoreEngine::calcDamageModifers(int charIndex, EoBMonsterInPlay *m, int item, int itemType, int useStrModifier) { - int s = (useStrModifier && (charIndex != -1)) ? getStrDamageModifier(charIndex) : 0; - if (item) { - EoBItemType *p = &_itemTypes[itemType]; - int t = m ? m->type : 0; - s += ((m && (_monsterProps[t].capsFlags & 1)) ? rollDice(p->dmgNumDiceL, p->dmgNumPipsL, p->dmgIncS /* bug in original code ? */) : - rollDice(p->dmgNumDiceS, p->dmgNumPipsS, p->dmgIncS)); - s += _items[item].value; - } else { - s += rollDice(1, 2); - } - - return (s < 0) ? 0 : s; -} - -bool EoBCoreEngine::trySavingThrow(void *target, int hpModifier, int level, int type, int race) { - static const int8 constMod[] = { 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5 }; - - if (type == 5) - return false; - - int s = getSaveThrowModifier(hpModifier, level, type); - if (((race == 3 || race == 5) && (type == 4 || type == 1 || type == 0)) || (race == 4 && (type == 4 || type == 1))) { - EoBCharacter *c = (EoBCharacter *)target; - s -= constMod[c->constitutionCur]; - } - - return rollDice(1, 20) >= s; -} - -bool EoBCoreEngine::specialAttackSavingThrow(int charIndex, int type) { - return trySavingThrow(&_characters[charIndex], _charClassModifier[_characters[charIndex].cClass], _characters[charIndex].level[0], type, _characters[charIndex].raceSex >> 1); -} - -int EoBCoreEngine::getSaveThrowModifier(int hpModifier, int level, int type) { - const uint8 *tbl = _saveThrowTables[hpModifier]; - if (_saveThrowLevelIndex[hpModifier] < level) - level = _saveThrowLevelIndex[hpModifier]; - level /= _saveThrowModDiv[hpModifier]; - level += (_saveThrowModExt[hpModifier] * type); - - return tbl[level]; -} - -bool EoBCoreEngine::calcDamageCheckItemType(int itemType) { - itemType = _itemTypes[itemType].extraProperties & 0x7F; - return (itemType == 2 || itemType == 3) ? true : false; -} - -int EoBCoreEngine::savingThrowReduceDamage(int savingThrowEffect, int damage) { - if (savingThrowEffect == 3) - return 0; - - if (savingThrowEffect == 0 || savingThrowEffect == 1) - return damage >> 1; - - return damage; -} - -bool EoBCoreEngine::tryMonsterAttackEvasion(EoBMonsterInPlay *m) { - return rollDice(1, 100) < _monsterProps[m->type].dmgModifierEvade ? true : false; -} - -int EoBCoreEngine::getStrHitChanceModifier(int charIndex) { - static const int8 strExtLimit[] = { 1, 51, 76, 91, 100 }; - static const int8 strExtMod[] = { 1, 2, 2, 2, 3 }; - static const int8 strMod[] = { -4, -3, -3, -2, -2, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 3, 4, 4, 5, 6, 7 }; - - int r = strMod[_characters[charIndex].strengthCur - 1]; - if (_characters[charIndex].strengthExtCur) { - for (int i = 0; i < 5; i++) { - if (_characters[charIndex].strengthExtCur >= strExtLimit[i]) - r = strExtMod[i]; - } - } - - return r; -} - -int EoBCoreEngine::getStrDamageModifier(int charIndex) { - static const int8 strExtLimit[] = { 1, 51, 76, 91, 100 }; - static const int8 strExtMod[] = { 3, 3, 4, 5, 6 }; - static const int8 strMod[] = { -3, -2, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 7, 8, 9, 10, 11, 12, 14 }; - - int r = strMod[_characters[charIndex].strengthCur - 1]; - if (_characters[charIndex].strengthExtCur) { - for (int i = 0; i < 5; i++) { - if (_characters[charIndex].strengthExtCur >= strExtLimit[i]) - r = strExtMod[i]; - } - } - - return r; -} - -int EoBCoreEngine::getDexHitChanceModifier(int charIndex) { - static const int8 dexMod[] = { -5, -4, -3, -2, -1, 0, 0, 0, 0, 0, 0, 1, 2, 2, 3, 3, 4, 4, 4 }; - return dexMod[_characters[charIndex].dexterityCur - 1]; -} - -int EoBCoreEngine::getMonsterAcHitChanceModifier(int charIndex, int monsterAc) { - int l = _characters[charIndex].level[0] - 1; - int cm = _charClassModifier[_characters[charIndex].cClass]; - - return (20 - ((l / _monsterAcHitChanceTable1[cm]) * _monsterAcHitChanceTable2[cm])) - monsterAc; -} - -void EoBCoreEngine::explodeMonster(EoBMonsterInPlay *m) { - m->flags |= 2; - if (getBlockDistance(m->block, _currentBlock) < 2) { - explodeObject(0, _currentBlock, 2); - for (int i = 0; i < 6; i++) - calcAndInflictCharacterDamage(i, 6, 6, 0, 8, 1, 0); - } else { - explodeObject(0, m->block, 2); - } - m->flags &= ~2; -} - -void EoBCoreEngine::snd_playSong(int track) { - _sound->playTrack(track); -} - -void EoBCoreEngine::snd_playSoundEffect(int track, int volume) { - if ((track < 1) || (_flags.gameID == GI_EOB2 && track > 119) || shouldQuit()) - return; - - _sound->playSoundEffect(track, volume); -} - -void EoBCoreEngine::snd_stopSound() { - _sound->haltTrack(); - _sound->stopAllSoundEffects(); -} - -void EoBCoreEngine::snd_fadeOut() { - _sound->beginFadeOut(); -} - -} // End of namespace Kyra - -#endif // ENABLE_EOB diff --git a/engines/kyra/eobcommon.h b/engines/kyra/eobcommon.h deleted file mode 100644 index 02627bf111..0000000000 --- a/engines/kyra/eobcommon.h +++ /dev/null @@ -1,1185 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef KYRA_EOBCOMMON_H -#define KYRA_EOBCOMMON_H - -#if defined(ENABLE_EOB) || defined(ENABLE_LOL) -#include "kyra/kyra_rpg.h" -#endif // (ENABLE_EOB || ENABLE_LOL) - -#ifdef ENABLE_EOB - -namespace Kyra { - -struct DarkMoonShapeDef { - int16 index; - uint8 x, y, w, h; -}; - -struct CreatePartyModButton { - uint8 encodeLabelX; - uint8 encodeLabelY; - uint8 labelW; - uint8 labelH; - uint8 labelX; - uint8 labelY; - uint8 bodyIndex; - uint8 destX; - uint8 destY; -}; - -struct EoBRect8 { - uint8 x; - uint8 y; - uint8 w; - uint8 h; -}; - -struct EoBChargenButtonDef { - uint8 x; - uint8 y; - uint8 w; - uint8 h; - uint8 keyCode; -}; - -struct EoBGuiButtonDef { - uint16 keyCode; - uint16 keyCode2; - uint16 flags; - uint16 x; - uint8 y; - uint16 w; - uint8 h; - uint16 arg; -}; - -struct EoBCharacter { - uint8 id; - uint8 flags; - char name[21]; - int8 strengthCur; - int8 strengthMax; - int8 strengthExtCur; - int8 strengthExtMax; - int8 intelligenceCur; - int8 intelligenceMax; - int8 wisdomCur; - int8 wisdomMax; - int8 dexterityCur; - int8 dexterityMax; - int8 constitutionCur; - int8 constitutionMax; - int8 charismaCur; - int8 charismaMax; - int16 hitPointsCur; - int16 hitPointsMax; - int8 armorClass; - uint8 disabledSlots; - uint8 raceSex; - uint8 cClass; - uint8 alignment; - int8 portrait; - uint8 food; - uint8 level[3]; - uint32 experience[3]; - uint8 *faceShape; - - int8 mageSpells[80]; - int8 clericSpells[80]; - uint32 mageSpellsAvailableFlags; - - Item inventory[27]; - uint32 timers[10]; - int8 events[10]; - uint8 effectsRemainder[4]; - uint32 effectFlags; - uint8 damageTaken; - int8 slotStatus[5]; -}; - -struct EoBItem { - uint8 nameUnid; - uint8 nameId; - uint8 flags; - int8 icon; - int8 type; - int8 pos; - int16 block; - Item next; - Item prev; - uint8 level; - int8 value; -}; - -struct EoBItemType { - uint16 invFlags; - uint16 handFlags; - int8 armorClass; - int8 allowedClasses; - int8 requiredHands; - int8 dmgNumDiceS; - int8 dmgNumPipsS; - int8 dmgIncS; - int8 dmgNumDiceL; - int8 dmgNumPipsL; - int8 dmgIncL; - uint8 unk1; - uint16 extraProperties; -}; - -struct SpriteDecoration { - uint8 *shp; - int16 x; - int16 y; -}; - -struct EoBMonsterProperty { - int8 armorClass; - int8 hitChance; - int8 level; - uint8 hpDcTimes; - uint8 hpDcPips; - uint8 hpDcBase; - uint8 attacksPerRound; - struct DmgDc { - uint8 times; - uint8 pips; - int8 base; - } dmgDc[3]; - uint16 immunityFlags; - uint32 capsFlags; - uint32 typeFlags; - int32 experience; - - uint8 u30; - int8 sound1; - int8 sound2; - uint8 numRemoteAttacks; - uint8 remoteWeaponChangeMode; - uint8 numRemoteWeapons; - - int8 remoteWeapons[5]; - - int8 tuResist; - uint8 dmgModifierEvade; - - uint8 decorations[3]; -}; - -struct EoBMonsterInPlay { - uint8 type; - uint8 unit; - uint16 block; - uint8 pos; - int8 dir; - uint8 animStep; - uint8 shpIndex; - int8 mode; - int8 f_9; - int8 curAttackFrame; - int8 spellStatusLeft; - int16 hitPointsMax; - int16 hitPointsCur; - uint16 dest; - uint16 randItem; - uint16 fixedItem; - uint8 flags; - uint8 idleAnimState; - uint8 curRemoteWeapon; - uint8 numRemoteAttacks; - int8 palette; - uint8 directionChanged; - uint8 stepsTillRemoteAttack; - uint8 sub; -}; - -struct ScriptTimer { - uint16 func; - uint16 ticks; - uint32 next; -}; - -struct EoBMenuDef { - int8 titleStrId; - uint8 dim; - uint8 firstButtonStrId; - int8 numButtons; - int8 titleCol; -}; -struct EoBMenuButtonDef { - int8 labelId; - int16 x; - int8 y; - uint8 width; - uint8 height; - int16 keyCode; - int16 flags; -}; - -class EoBInfProcessor; - -class EoBCoreEngine : public KyraRpgEngine { -friend class TextDisplayer_rpg; -friend class GUI_EoB; -friend class Debugger_EoB; -friend class EoBInfProcessor; -friend class DarkmoonSequenceHelper; -friend class CharacterGenerator; -friend class TransferPartyWiz; -public: - EoBCoreEngine(OSystem *system, const GameFlags &flags); - virtual ~EoBCoreEngine(); - - virtual void initKeymap(); - - Screen *screen() { return _screen; } - GUI *gui() const { return _gui; } - -protected: - // Startup - virtual Common::Error init(); - Common::Error go(); - - // Main Menu, Intro, Finale - virtual int mainMenu() = 0; - virtual void seq_xdeath() {} - virtual void seq_playFinale() = 0; - bool _playFinale; - - //Init, config - void loadItemsAndDecorationsShapes(); - void releaseItemsAndDecorationsShapes(); - - void initButtonData(); - void initMenus(); - void initStaticResource(); - virtual void initSpells(); - - void registerDefaultSettings(); - void readSettings(); - void writeSettings(); - - const uint8 **_largeItemShapes; - const uint8 **_smallItemShapes; - const uint8 **_thrownItemShapes; - const int _numLargeItemShapes; - const int _numSmallItemShapes; - const int _numThrownItemShapes; - const int _numItemIconShapes; - - const uint8 **_spellShapes; - const uint8 **_firebeamShapes; - const uint8 *_redSplatShape; - const uint8 *_greenSplatShape; - const uint8 **_wallOfForceShapes; - const uint8 **_teleporterShapes; - const uint8 **_sparkShapes; - const uint8 *_deadCharShape; - const uint8 *_disabledCharGrid; - const uint8 *_blackBoxSmallGrid; - const uint8 *_weaponSlotGrid; - const uint8 *_blackBoxWideGrid; - const uint8 *_lightningColumnShape; - - uint8 *_itemsOverlay; - static const uint8 _itemsOverlayCGA[]; - - static const uint8 _teleporterShapeDefs[]; - static const uint8 _wallOfForceShapeDefs[]; - - const char *const *_mainMenuStrings; - - // Main loop - virtual void startupNew(); - virtual void startupLoad() = 0; - void runLoop(); - void update() { screen()->updateScreen(); } - bool checkPartyStatus(bool handleDeath); - - bool _runFlag; - - // Character generation / party transfer - bool startCharacterGeneration(); - bool startPartyTransfer(); - - uint8 **_faceShapes; - - static const int8 _characterClassType[]; - static const uint8 _hpIncrPerLevel[]; - static const uint8 _numLevelsPerClass[]; - static const int16 _hpConstModifiers[]; - static const uint8 _charClassModifier[]; - - const uint8 *_classModifierFlags; - - // timers - void setupTimers(); - void setCharEventTimer(int charIndex, uint32 countdown, int evnt, int updateExistingTimer); - void deleteCharEventTimer(int charIndex, int evnt); - void setupCharacterTimers(); - void advanceTimers(uint32 millis); - - void timerProcessMonsters(int timerNum); - void timerSpecialCharacterUpdate(int timerNum); - void timerProcessFlyingObjects(int timerNum); - void timerProcessCharacterExchange(int timerNum); - void timerUpdateTeleporters(int timerNum); - void timerUpdateFoodStatus(int timerNum); - void timerUpdateMonsterIdleAnim(int timerNum); - - uint8 getClock2Timer(int index) { return index < _numClock2Timers ? _clock2Timers[index] : 0; } - uint8 getNumClock2Timers() { return _numClock2Timers; } - - static const uint8 _clock2Timers[]; - static const uint8 _numClock2Timers; - - int32 _restPartyElapsedTime; - - // Mouse - void setHandItem(Item itemIndex); - - // Characters - int getDexterityArmorClassModifier(int dexterity); - int generateCharacterHitpointsByLevel(int charIndex, int levelIndex); - int getClassAndConstHitpointsModifier(int cclass, int constitution); - int getCharacterClassType(int cclass, int levelIndex); - int getModifiedHpLimits(int hpModifier, int constModifier, int level, bool mode); - Common::String getCharStrength(int str, int strExt); - int testCharacter(int16 index, int flags); - int getNextValidCharIndex(int curCharIndex, int searchStep); - - void recalcArmorClass(int index); - int validateWeaponSlotItem(int index, int slot); - - int getClericPaladinLevel(int index); - int getMageLevel(int index); - int getCharacterLevelIndex(int type, int cClass); - - int countCharactersWithSpecificItems(int16 itemType, int16 itemValue); - int checkInventoryForItem(int character, int16 itemType, int16 itemValue); - void modifyCharacterHitpoints(int character, int16 points); - void neutralizePoison(int character); - - void npcSequence(int npcIndex); - virtual void drawNpcScene(int npcIndex) = 0; - virtual void runNpcDialogue(int npcIndex) = 0; - void initNpc(int npcIndex); - int npcJoinDialogue(int npcIndex, int queryJoinTextId, int confirmJoinTextId, int noJoinTextId); - int prepareForNewPartyMember(int16 itemType, int16 itemValue); - void dropCharacter(int charIndex); - void removeCharacterFromParty(int charIndex); - void exchangeCharacters(int charIndex1, int charIndex2); - - void increasePartyExperience(int16 points); - void increaseCharacterExperience(int charIndex, int32 points); - uint32 getRequiredExperience(int cClass, int levelIndex, int level); - void increaseCharacterLevel(int charIndex, int levelIndex); - - void setWeaponSlotStatus(int charIndex, int mode, int slot); - - EoBCharacter *_characters; - Common::String _strenghtStr; - int _castScrollSlot; - int _exchangeCharacterId; - - const char *const *_levelGainStrings; - const uint32 *_expRequirementTables[6]; - - const uint8 *_saveThrowTables[6]; - const uint8 *_saveThrowLevelIndex; - const uint8 *_saveThrowModDiv; - const uint8 *_saveThrowModExt; - - const EoBCharacter *_npcPreset; - int _npcSequenceSub; - bool _partyResting; - bool _loading; - - // Items - void loadItemDefs(); - Item duplicateItem(Item itemIndex); - void setItemPosition(Item *itemQueue, int block, Item item, int pos); - Item createItemOnCurrentBlock(Item itemIndex); - void createInventoryItem(EoBCharacter *c, Item itemIndex, int16 itemValue, int preferedInventorySlot); - int deleteInventoryItem(int charIndex, int slot); - void deleteBlockItem(uint16 block, int type); - int validateInventorySlotForItem(Item item, int charIndex, int slot); - int stripPartyItems(int16 itemType, int16 itemValue, int handleValueMode, int numItems); - bool deletePartyItems(int16 itemType, int16 itemValue); - virtual void updateUsedCharacterHandItem(int charIndex, int slot) = 0; - int itemUsableByCharacter(int charIndex, Item item); - int countQueuedItems(Item itemQueue, int16 id, int16 type, int count, int includeFlyingItems); - int getQueuedItem(Item *items, int pos, int id); - void printFullItemName(Item item); - void identifyQueuedItems(Item itemQueue); - void drawItemIconShape(int pageNum, Item itemId, int x, int y); - bool isMagicEffectItem(Item itemIndex); - bool checkInventoryForRings(int charIndex, int itemValue); - void eatItemInHand(int charIndex); - - bool launchObject(int charIndex, Item item, uint16 startBlock, int startPos, int dir, int type); - void launchMagicObject(int charIndex, int type, uint16 startBlock, int startPos, int dir); - bool updateObjectFlight(EoBFlyingObject *fo, int block, int pos); - bool updateFlyingObjectHitTest(EoBFlyingObject *fo, int block, int pos); - void explodeObject(EoBFlyingObject *fo, int block, Item item); - void endObjectFlight(EoBFlyingObject *fo); - void checkFlyingObjects(); - - void reloadWeaponSlot(int charIndex, int slotIndex, int itemType, int arrowOrDagger); - - EoBItem *_items; - uint16 _numItems; - EoBItemType *_itemTypes; - char **_itemNames; - uint16 _numItemNames; - uint32 _partyEffectFlags; - Item _lastUsedItem; - - const uint16 *_slotValidationFlags; - const int8 *_projectileWeaponAmmoTypes; - const uint8 *_wandTypes; - - EoBFlyingObject *_flyingObjects; - const uint8 *_drawObjPosIndex; - const uint8 *_flightObjFlipIndex; - const int8 *_flightObjShpMap; - const int8 *_flightObjSclIndex; - - const uint8 *_expObjectTlMode; - const uint8 *_expObjectTblIndex; - const uint8 *_expObjectShpStart; - const uint8 *_expObjectAnimTbl1; - int _expObjectAnimTbl1Size; - const uint8 *_expObjectAnimTbl2; - int _expObjectAnimTbl2Size; - const uint8 *_expObjectAnimTbl3; - int _expObjectAnimTbl3Size; - - const char *const *_ascii2SjisTables; - const char *const *_ascii2SjisTables2; - - // Monsters - void loadMonsterShapes(const char *filename, int monsterIndex, bool hasDecorations, int encodeTableIndex); - void releaseMonsterShapes(int first, int num); - uint8 *loadTownsShape(Common::SeekableReadStream *stream); - virtual void generateMonsterPalettes(const char *file, int16 monsterIndex) {} - virtual void loadMonsterDecoration(Common::SeekableReadStream *stream, int16 monsterIndex) {} - const uint8 *loadMonsterProperties(const uint8 *data); - const uint8 *loadActiveMonsterData(const uint8 *data, int level); - void initMonster(int index, int unit, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int i, int randItem, int fixedItem); - void placeMonster(EoBMonsterInPlay *m, uint16 block, int dir); - virtual void replaceMonster(int b, uint16 block, int pos, int dir, int type, int shpIndex, int mode, int h2, int randItem, int fixedItem) = 0; - void killMonster(EoBMonsterInPlay *m, bool giveExperience); - virtual bool killMonsterExtra(EoBMonsterInPlay *m) = 0; - int countSpecificMonsters(int type); - void updateAttackingMonsterFlags(); - - const int8 *getMonstersOnBlockPositions(uint16 block); - int getClosestMonster(int charIndex, int block); - - bool blockHasMonsters(uint16 block); - bool isMonsterOnPos(EoBMonsterInPlay *m, uint16 block, int pos, int checkPos4); - const int16 *findBlockMonsters(uint16 block, int pos, int dir, int blockDamage, int singleTargetCheckAdjacent); - - void drawBlockObject(int flipped, int page, const uint8 *shape, int x, int y, int sd, uint8 *ovl = 0); - void drawMonsterShape(const uint8 *shape, int x, int y, int flipped, int flags, int palIndex); - void flashMonsterShape(EoBMonsterInPlay *m); - void updateAllMonsterShapes(); - void drawBlockItems(int index); - void drawDoor(int index); - virtual void drawDoorIntern(int type, int index, int x, int y, int w, int wall, int mDim, int16 y1, int16 y2) = 0; - void drawMonsters(int index); - void drawWallOfForce(int index); - void drawFlyingObjects(int index); - void drawTeleporter(int index); - - void updateMonsters(int unit); - void updateMonsterDest(EoBMonsterInPlay *m); - void updateMonsterAttackMode(EoBMonsterInPlay *m); - void updateAllMonsterDests(); - void turnFriendlyMonstersHostile(); - int getNextMonsterDirection(int curBlock, int destBlock); - int getNextMonsterPos(EoBMonsterInPlay *m, int block); - int findFreeMonsterPos(int block, int size); - void updateMoveMonster(EoBMonsterInPlay *m); - bool updateMonsterTryDistanceAttack(EoBMonsterInPlay *m); - bool updateMonsterTryCloseAttack(EoBMonsterInPlay *m, int block); - void walkMonster(EoBMonsterInPlay *m, int destBlock); - bool walkMonsterNextStep(EoBMonsterInPlay *m, int destBlock, int direction); - void updateMonsterFollowPath(EoBMonsterInPlay *m, int turnSteps); - void updateMonstersStraying(EoBMonsterInPlay *m, int a); - void updateMonstersSpellStatus(EoBMonsterInPlay *m); - void setBlockMonsterDirection(int block, int dir); - - uint8 *_monsterFlashOverlay; - uint8 *_monsterStoneOverlay; - - SpriteDecoration *_monsterDecorations; - EoBMonsterProperty *_monsterProps; - - EoBMonsterInPlay *_monsters; - - const int8 *_monsterStepTable0; - const int8 *_monsterStepTable1; - const int8 *_monsterStepTable2; - const int8 *_monsterStepTable3; - const uint8 *_monsterCloseAttPosTable1; - const uint8 *_monsterCloseAttPosTable2; - const int8 *_monsterCloseAttUnkTable; - const uint8 *_monsterCloseAttChkTable1; - const uint8 *_monsterCloseAttChkTable2; - const uint8 *_monsterCloseAttDstTable1; - const uint8 *_monsterCloseAttDstTable2; - - const uint8 *_monsterProximityTable; - const uint8 *_findBlockMonstersTable; - const char *const *_monsterDustStrings; - - const uint8 *_enemyMageSpellList; - const uint8 *_enemyMageSfx; - const uint8 *_beholderSpellList; - const uint8 *_beholderSfx; - const char *const *_monsterSpecAttStrings; - - const int8 *_monsterFrmOffsTable1; - const int8 *_monsterFrmOffsTable2; - - const uint16 *_encodeMonsterShpTable; - const uint8 _teleporterWallId; - - const int16 *_wallOfForceDsX; - const uint8 *_wallOfForceDsY; - const uint8 *_wallOfForceDsNumW; - const uint8 *_wallOfForceDsNumH; - const uint8 *_wallOfForceShpId; - - const int8 *_monsterDirChangeTable; - - // Level - void loadLevel(int level, int sub); - void readLevelFileData(int level); - Common::String initLevelData(int sub); - void addLevelItems(); - void loadVcnData(const char *file, const uint8 *cgaMapping); - void loadBlockProperties(const char *mazFile); - const uint8 *getBlockFileData(int levelIndex = 0); - Common::String getBlockFileName(int levelIndex, int sub); - const uint8 *getBlockFileData(const char *mazFile); - void loadDecorations(const char *cpsFile, const char *decFile); - void assignWallsAndDecorations(int wallIndex, int vmpIndex, int decDataIndex, int specialType, int flags); - void releaseDecorations(); - void releaseDoorShapes(); - void toggleWallState(int wall, int flags); - virtual void loadDoorShapes(int doorType1, int shapeId1, int doorType2, int shapeId2) = 0; - virtual const uint8 *loadDoorShapes(const char *filename, int doorIndex, const uint8 *shapeDefs) = 0; - - void drawScene(int refresh); - void drawSceneShapes(int start = 0); - void drawDecorations(int index); - - int calcNewBlockPositionAndTestPassability(uint16 curBlock, uint16 direction); - void notifyBlockNotPassable(); - void moveParty(uint16 block); - - int clickedDoorSwitch(uint16 block, uint16 direction); - int clickedDoorPry(uint16 block, uint16 direction); - int clickedDoorNoPry(uint16 block, uint16 direction); - int clickedNiche(uint16 block, uint16 direction); - - int specialWallAction(int block, int direction); - - void openDoor(int block); - void closeDoor(int block); - - int16 _doorType[2]; - int16 _noDoorSwitch[2]; - - EoBRect8 *_levelDecorationRects; - SpriteDecoration *_doorSwitches; - - int8 _currentSub; - Common::String _curGfxFile; - Common::String _curBlockFile; - - uint32 _drawSceneTimer; - uint32 _flashShapeTimer; - uint32 _envAudioTimer; - uint16 _teleporterPulse; - - Common::Array _dscWallMapping; - const int16 *_dscShapeCoords; - - const uint8 *_dscItemPosIndex; - const int16 *_dscItemShpX; - const uint8 *_dscItemScaleIndex; - const uint8 *_dscItemTileIndex; - const uint8 *_dscItemShapeMap; - - const uint8 *_dscDoorScaleMult1; - const uint8 *_dscDoorScaleMult2; - const uint8 *_dscDoorScaleMult3; - const uint8 *_dscDoorY1; - const uint8 *_dscDoorXE; - - uint8 *_greenFadingTable; - uint8 *_blueFadingTable; - uint8 *_lightBlueFadingTable; - uint8 *_blackFadingTable; - uint8 *_greyFadingTable; - - const uint8 *_wllFlagPreset; - int _wllFlagPresetSize; - const uint8 *_teleporterShapeCoords; - const int8 *_portalSeq; - - // Script - void runLevelScript(int block, int flags); - void setScriptFlags(uint32 flags); - void clearScriptFlags(uint32 flags); - bool checkScriptFlags(uint32 flags); - - const uint8 *initScriptTimers(const uint8 *pos); - void updateScriptTimers(); - virtual void updateScriptTimersExtra() {} - - EoBInfProcessor *_inf; - int _stepCounter; - int _stepsUntilScriptCall; - ScriptTimer _scriptTimers[5]; - int _scriptTimersCount; - uint8 _scriptTimersMode; - - // Gui - void gui_drawPlayField(bool refresh); - void gui_restorePlayField(); - void gui_drawAllCharPortraitsWithStats(); - void gui_drawCharPortraitWithStats(int index); - void gui_drawFaceShape(int index); - void gui_drawWeaponSlot(int charIndex, int slot); - void gui_drawWeaponSlotStatus(int x, int y, int status); - void gui_drawHitpoints(int index); - void gui_drawFoodStatusGraph(int index); - void gui_drawHorizontalBarGraph(int x, int y, int w, int h, int32 curVal, int32 maxVal, int col1, int col2); - void gui_drawCharPortraitStatusFrame(int index); - void gui_drawInventoryItem(int slot, int special, int pageNum); - void gui_drawCompass(bool force); - void gui_drawDialogueBox(); - void gui_drawSpellbook(); - void gui_drawSpellbookScrollArrow(int x, int y, int direction); - void gui_updateSlotAfterScrollUse(); - void gui_updateControls(); - void gui_toggleButtons(); - void gui_setPlayFieldButtons(); - void gui_setInventoryButtons(); - void gui_setStatsListButtons(); - void gui_setSwapCharacterButtons(); - void gui_setCastOnWhomButtons(); - void gui_initButton(int index, int x = -1, int y = -1, int val = -1); - Button *gui_getButton(Button *buttonList, int index); - - int clickedInventoryNextPage(Button *button); - int clickedPortraitRestore(Button *button); - int clickedCharPortraitDefault(Button *button); - int clickedCamp(Button *button); - int clickedSceneDropPickupItem(Button *button); - int clickedCharPortrait2(Button *button); - int clickedWeaponSlot(Button *button); - int clickedCharNameLabelRight(Button *button); - int clickedInventorySlot(Button *button); - int clickedEatItem(Button *button); - int clickedInventoryPrevChar(Button *button); - int clickedInventoryNextChar(Button *button); - int clickedSpellbookTab(Button *button); - int clickedSpellbookList(Button *button); - int clickedCastSpellOnCharacter(Button *button); - int clickedUpArrow(Button *button); - int clickedDownArrow(Button *button); - int clickedLeftArrow(Button *button); - int clickedRightArrow(Button *button); - int clickedTurnLeftArrow(Button *button); - int clickedTurnRightArrow(Button *button); - int clickedAbortCharSwitch(Button *button); - int clickedSceneThrowItem(Button *button); - int clickedSceneSpecial(Button *button); - int clickedSpellbookAbort(Button *button); - int clickedSpellbookScroll(Button *button); - int clickedUnk(Button *button); - - void gui_processCharPortraitClick(int index); - void gui_processWeaponSlotClickLeft(int charIndex, int slotIndex); - void gui_processWeaponSlotClickRight(int charIndex, int slotIndex); - void gui_processInventorySlotClick(int slot); - - static const uint8 _buttonList1[]; - int _buttonList1Size; - static const uint8 _buttonList2[]; - int _buttonList2Size; - static const uint8 _buttonList3[]; - int _buttonList3Size; - static const uint8 _buttonList4[]; - int _buttonList4Size; - static const uint8 _buttonList5[]; - int _buttonList5Size; - static const uint8 _buttonList6[]; - int _buttonList6Size; - static const uint8 _buttonList7[]; - int _buttonList7Size; - static const uint8 _buttonList8[]; - int _buttonList8Size; - - EoBGuiButtonDef *_buttonDefs; - - const char *const *_characterGuiStringsHp; - const char *const *_characterGuiStringsWp; - const char *const *_characterGuiStringsWr; - const char *const *_characterGuiStringsSt; - const char *const *_characterGuiStringsIn; - - const char *const *_characterStatusStrings7; - const char *const *_characterStatusStrings8; - const char *const *_characterStatusStrings9; - const char *const *_characterStatusStrings12; - const char *const *_characterStatusStrings13; - - const uint16 *_inventorySlotsX; - const uint8 *_inventorySlotsY; - const uint8 **_compassShapes; - uint8 _charExchangeSwap; - bool _configHpBarGraphs; - bool _configMouseBtSwap; - - Graphics::Surface _thumbNail; - - // text - void setupDialogueButtons(int presetfirst, int numStr, va_list &args); - void initDialogueSequence(); - void restoreAfterDialogueSequence(); - void drawSequenceBitmap(const char *file, int destRect, int x1, int y1, int flags); - int runDialogue(int dialogueTextId, int numStr, ...); - - char _dialogueLastBitmap[13]; - int _moveCounter; - - const char *const *_chargenStatStrings; - const char *const *_chargenRaceSexStrings; - const char *const *_chargenClassStrings; - const char *const *_chargenAlignmentStrings; - - const char *const *_pryDoorStrings; - const char *const *_warningStrings; - - const char *const *_ripItemStrings; - const char *const *_cursedString; - const char *const *_enchantedString; - const char *const *_magicObjectStrings; - const char *const *_magicObjectString5; - const char *const *_patternSuffix; - const char *const *_patternGrFix1; - const char *const *_patternGrFix2; - const char *const *_validateArmorString; - const char *const *_validateCursedString; - const char *const *_validateNoDropString; - const char *const *_potionStrings; - const char *const *_wandStrings; - const char *const *_itemMisuseStrings; - - const char *const *_suffixStringsRings; - const char *const *_suffixStringsPotions; - const char *const *_suffixStringsWands; - - const char *const *_takenStrings; - const char *const *_potionEffectStrings; - - const char *const *_yesNoStrings; - const char *const *_npcMaxStrings; - const char *const *_okStrings; - const char *const *_npcJoinStrings; - const char *const *_cancelStrings; - const char *const *_abortStrings; - - // Rest party - void restParty_displayWarning(const char *str); - bool restParty_updateMonsters(); - int restParty_getCharacterWithLowestHp(); - bool restParty_checkHealSpells(int charIndex); - bool restParty_checkSpellsToLearn(); - virtual void restParty_npc() {} - virtual bool restParty_extraAbortCondition(); - - // misc - void delay(uint32 millis, bool doUpdate = false, bool isMainLoop = false); - - void displayParchment(int id); - int countResurrectionCandidates(); - - void seq_portal(); - bool checkPassword(); - - Common::String convertAsciiToSjis(Common::String str); - - virtual int resurrectionSelectDialogue() = 0; - virtual void useHorn(int charIndex, int weaponSlot) {} - virtual bool checkPartyStatusExtra() = 0; - virtual void drawLightningColumn() {} - virtual int charSelectDialogue() { return -1; } - virtual void characterLevelGain(int charIndex) {} - - Common::Error loadGameState(int slot); - Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail); - - const uint8 *_cgaMappingDefault; - const uint8 *_cgaMappingAlt; - const uint8 *_cgaMappingInv; - const uint8 *_cgaMappingItemsL; - const uint8 *_cgaMappingItemsS; - const uint8 *_cgaMappingThrown; - const uint8 *_cgaMappingIcons; - const uint8 *_cgaMappingDeco; - const uint8 *_cgaMappingLevel[5]; - const uint8 *_cgaLevelMappingIndex; - - bool _enableHiResDithering; - - // Default parameters will import all present original save files and push them to the top of the save dialog. - bool importOriginalSaveFile(int destSlot, const char *sourceFile = 0); - Common::String readOriginalSaveFile(Common::String &file); - bool saveAsOriginalSaveFile(int slot = -1); - - void *generateMonsterTempData(LevelTempData *tmp); - void restoreMonsterTempData(LevelTempData *tmp); - void releaseMonsterTempData(LevelTempData *tmp); - void *generateWallOfForceTempData(LevelTempData *tmp); - void restoreWallOfForceTempData(LevelTempData *tmp); - void releaseWallOfForceTempData(LevelTempData *tmp); - - const char *const *_saveLoadStrings; - - const uint8 *_mnDef; - const char *const *_mnWord; - const char *const *_mnPrompt; - int _mnNumWord; - - int _rrCount; - const char *_rrNames[10]; - int8 _rrId[10]; - - bool _allowSkip; - bool _allowImport; - - Screen_EoB *_screen; - GUI_EoB *_gui; - - // fight - void useSlotWeapon(int charIndex, int slotIndex, Item item); - int closeDistanceAttack(int charIndex, Item item); - int thrownAttack(int charIndex, int slotIndex, Item item); - int projectileWeaponAttack(int charIndex, Item item); - - void inflictMonsterDamage(EoBMonsterInPlay *m, int damage, bool giveExperience); - void calcAndInflictMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect); - void calcAndInflictCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int savingThrowEffect); - int calcCharacterDamage(int charIndex, int times, int itemOrPips, int useStrModifierOrBase, int flags, int savingThrowType, int damageType); - void inflictCharacterDamage(int charIndex, int damage); - - bool characterAttackHitTest(int charIndex, int monsterIndex, int item, int attackType); - bool monsterAttackHitTest(EoBMonsterInPlay *m, int charIndex); - bool flyingObjectMonsterHit(EoBFlyingObject *fo, int monsterIndex); - bool flyingObjectPartyHit(EoBFlyingObject *fo); - - void monsterCloseAttack(EoBMonsterInPlay *m); - void monsterSpellCast(EoBMonsterInPlay *m, int type); - void statusAttack(int charIndex, int attackStatusFlags, const char *attackStatusString, int savingThrowType, uint32 effectDuration, int restoreEvent, int noRefresh); - - int calcMonsterDamage(EoBMonsterInPlay *m, int times, int pips, int offs, int flags, int savingThrowType, int savingThrowEffect); - int calcDamageModifers(int charIndex, EoBMonsterInPlay *m, int item, int itemType, int useStrModifier); - bool trySavingThrow(void *target, int hpModifier, int level, int type, int race); - bool specialAttackSavingThrow(int charIndex, int type); - int getSaveThrowModifier(int hpModifier, int level, int type); - bool calcDamageCheckItemType(int itemType); - int savingThrowReduceDamage(int savingThrowEffect, int damage); - bool tryMonsterAttackEvasion(EoBMonsterInPlay *m); - int getStrHitChanceModifier(int charIndex); - int getStrDamageModifier(int charIndex); - int getDexHitChanceModifier(int charIndex); - int getMonsterAcHitChanceModifier(int charIndex, int monsterAc); - void explodeMonster(EoBMonsterInPlay *m); - - int _dstMonsterIndex; - bool _preventMonsterFlash; - int16 _foundMonstersArray[5]; - int8 _monsterBlockPosArray[6]; - const uint8 *_monsterAcHitChanceTable1; - const uint8 *_monsterAcHitChanceTable2; - - // magic - void useMagicBookOrSymbol(int charIndex, int type); - void useMagicScroll(int charIndex, int type, int weaponSlot); - void usePotion(int charIndex, int weaponSlot); - void useWand(int charIndex, int weaponSlot); - - virtual void turnUndeadAuto() {} - virtual void turnUndeadAutoHit() {} - - void castSpell(int spell, int weaponSlot); - void removeCharacterEffect(int spell, int charIndex, int showWarning); - void removeAllCharacterEffects(int charIndex); - void castOnWhomDialogue(); - void startSpell(int spell); - - void sparkEffectDefensive(int charIndex); - void sparkEffectOffensive(); - void setSpellEventTimer(int spell, int timerBaseFactor, int timerLength, int timerLevelFactor, int updateExistingTimer); - void sortCharacterSpellList(int charIndex); - - bool magicObjectDamageHit(EoBFlyingObject *fo, int dcTimes, int dcPips, int dcOffs, int level); - bool magicObjectStatusHit(EoBMonsterInPlay *m, int type, bool tryEvade, int mod); - bool turnUndeadHit(EoBMonsterInPlay *m, int hitChance, int casterLevel); - void causeWounds(int dcTimes, int dcPips, int dcOffs); - - int getMagicWeaponSlot(int charIndex); - int createMagicWeaponType(int invFlags, int handFlags, int armorClass, int allowedClasses, int dmgNum, int dmgPips, int dmgInc, int extraProps); - Item createMagicWeaponItem(int flags, int icon, int value, int type); - void removeMagicWeaponItem(Item item); - - void updateWallOfForceTimers(); - void destroyWallOfForce(int index); - - int findSingleSpellTarget(int dist); - - int findFirstCharacterSpellTarget(); - int findNextCharacterSpellTarget(int curCharIndex); - int charDeathSavingThrow(int charIndex, int div); - - void printWarning(const char *str); - void printNoEffectWarning(); - - void spellCallback_start_empty() {} - bool spellCallback_end_empty(void *) { return true; } - void spellCallback_start_armor(); - void spellCallback_start_burningHands(); - void spellCallback_start_detectMagic(); - bool spellCallback_end_detectMagic(void *); - void spellCallback_start_magicMissile(); - bool spellCallback_end_magicMissile(void *obj); - void spellCallback_start_shockingGrasp(); - bool spellCallback_end_shockingGraspFlameBlade(void *obj); - void spellCallback_start_improvedIdentify(); - void spellCallback_start_melfsAcidArrow(); - bool spellCallback_end_melfsAcidArrow(void *obj); - void spellCallback_start_dispelMagic(); - void spellCallback_start_fireball(); - bool spellCallback_end_fireball(void *obj); - void spellCallback_start_flameArrow(); - bool spellCallback_end_flameArrow(void *obj); - void spellCallback_start_holdPerson(); - bool spellCallback_end_holdPerson(void *obj); - void spellCallback_start_lightningBolt(); - bool spellCallback_end_lightningBolt(void *obj); - void spellCallback_start_vampiricTouch(); - bool spellCallback_end_vampiricTouch(void *obj); - void spellCallback_start_fear(); - void spellCallback_start_iceStorm(); - bool spellCallback_end_iceStorm(void *obj); - void spellCallback_start_stoneSkin(); - void spellCallback_start_removeCurse(); - void spellCallback_start_coneOfCold(); - void spellCallback_start_holdMonster(); - bool spellCallback_end_holdMonster(void *obj); - void spellCallback_start_wallOfForce(); - void spellCallback_start_disintegrate(); - void spellCallback_start_fleshToStone(); - void spellCallback_start_stoneToFlesh(); - void spellCallback_start_trueSeeing(); - bool spellCallback_end_trueSeeing(void *); - void spellCallback_start_slayLiving(); - void spellCallback_start_powerWordStun(); - void spellCallback_start_causeLightWounds(); - void spellCallback_start_cureLightWounds(); - void spellCallback_start_aid(); - bool spellCallback_end_aid(void *obj); - void spellCallback_start_flameBlade(); - void spellCallback_start_slowPoison(); - bool spellCallback_end_slowPoison(void *obj); - void spellCallback_start_createFood(); - void spellCallback_start_removeParalysis(); - void spellCallback_start_causeSeriousWounds(); - void spellCallback_start_cureSeriousWounds(); - void spellCallback_start_neutralizePoison(); - void spellCallback_start_causeCriticalWounds(); - void spellCallback_start_cureCriticalWounds(); - void spellCallback_start_flameStrike(); - bool spellCallback_end_flameStrike(void *obj); - void spellCallback_start_raiseDead(); - void spellCallback_start_harm(); - void spellCallback_start_heal(); - void spellCallback_start_layOnHands(); - void spellCallback_start_turnUndead(); - bool spellCallback_end_monster_lightningBolt(void *obj); - bool spellCallback_end_monster_fireball1(void *obj); - bool spellCallback_end_monster_fireball2(void *obj); - bool spellCallback_end_monster_deathSpell(void *obj); - bool spellCallback_end_monster_disintegrate(void *obj); - bool spellCallback_end_monster_causeCriticalWounds(void *obj); - bool spellCallback_end_monster_fleshToStone(void *obj); - - int8 _openBookSpellLevel; - int8 _openBookSpellSelectedItem; - int8 _openBookSpellListOffset; - uint8 _openBookChar; - uint8 _openBookType; - uint8 _openBookCharBackup; - uint8 _openBookTypeBackup; - uint8 _openBookCasterLevel; - const char *const *_openBookSpellList; - int8 *_openBookAvailableSpells; - uint8 _activeSpellCharId; - uint8 _activeSpellCharacterPos; - int _activeSpell; - int _characterSpellTarget; - bool _returnAfterSpellCallback; - - typedef void (EoBCoreEngine::*SpellStartCallback)(); - typedef bool (EoBCoreEngine::*SpellEndCallback)(void *obj); - - struct EoBSpell { - const char *name; - SpellStartCallback startCallback; - uint16 flags; - const uint16 *timingPara; - SpellEndCallback endCallback; - uint8 sound; - uint32 effectFlags; - uint16 damageFlags; - }; - - EoBSpell *_spells; - int _numSpells; - - struct WallOfForce { - uint16 block; - uint32 duration; - }; - - WallOfForce *_wallsOfForce; - - const char *const *_bookNumbers; - const char *const *_mageSpellList; - int _mageSpellListSize; - int _clericSpellOffset; - const char *const *_clericSpellList; - const char *const *_spellNames; - const char *const *_magicStrings1; - const char *const *_magicStrings2; - const char *const *_magicStrings3; - const char *const *_magicStrings4; - const char *const *_magicStrings6; - const char *const *_magicStrings7; - const char *const *_magicStrings8; - - uint8 *_spellAnimBuffer; - - const uint8 *_sparkEffectDefSteps; - const uint8 *_sparkEffectDefSubSteps; - const uint8 *_sparkEffectDefShift; - const uint8 *_sparkEffectDefAdd; - const uint8 *_sparkEffectDefX; - const uint8 *_sparkEffectDefY; - const uint32 *_sparkEffectOfFlags1; - const uint32 *_sparkEffectOfFlags2; - const uint8 *_sparkEffectOfShift; - const uint8 *_sparkEffectOfX; - const uint8 *_sparkEffectOfY; - - const uint8 *_magicFlightObjectProperties; - const uint8 *_turnUndeadEffect; - const uint8 *_burningHandsDest; - const int8 *_coneOfColdDest1; - const int8 *_coneOfColdDest2; - const int8 *_coneOfColdDest3; - const int8 *_coneOfColdDest4; - const uint8 *_coneOfColdGfxTbl; - int _coneOfColdGfxTblSize; - - // Menu - EoBMenuDef *_menuDefs; - const EoBMenuButtonDef *_menuButtonDefs; - - bool _configMouse; - bool _config2431; - - const char *const *_menuStringsMain; - const char *const *_menuStringsSaveLoad; - const char *const *_menuStringsOnOff; - const char *const *_menuStringsSpells; - const char *const *_menuStringsRest; - const char *const *_menuStringsDrop; - const char *const *_menuStringsExit; - const char *const *_menuStringsStarve; - const char *const *_menuStringsScribe; - const char *const *_menuStringsDrop2; - const char *const *_menuStringsHead; - const char *const *_menuStringsPoison; - const char *const *_menuStringsMgc; - const char *const *_menuStringsPrefs; - const char *const *_menuStringsRest2; - const char *const *_menuStringsRest3; - const char *const *_menuStringsRest4; - const char *const *_menuStringsDefeat; - const char *_errorSlotEmptyString; - const char *_errorSlotNoNameString; - const char *_menuOkString; - const char *const *_2431Strings; - const char *const *_katakanaLines; - const char *const *_katakanaSelectStrings; - const char *const *_menuStringsTransfer; - const char *const *_transferStringsScummVM; - const char *const *_menuStringsSpec; - const char *const *_menuStringsSpellNo; - const char *const *_menuYesNoStrings; - const char *const *_saveNamePatterns; - - const uint8 *_spellLevelsMage; - int _spellLevelsMageSize; - const uint8 *_spellLevelsCleric; - int _spellLevelsClericSize; - const uint8 *_numSpellsCleric; - const uint8 *_numSpellsWisAdj; - const uint8 *_numSpellsPal; - const uint8 *_numSpellsMage; - - // sound - void snd_playSong(int id); - void snd_playSoundEffect(int id, int volume=0xFF); - void snd_stopSound(); - void snd_fadeOut(); - - // keymap - static const char *const kKeymapName; -}; - -} // End of namespace Kyra - -#endif // ENABLE_EOB - -#endif diff --git a/engines/kyra/graphics/animator_hof.cpp b/engines/kyra/graphics/animator_hof.cpp new file mode 100644 index 0000000000..0b8db62a9b --- /dev/null +++ b/engines/kyra/graphics/animator_hof.cpp @@ -0,0 +1,313 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_hof.h" +#include "kyra/graphics/wsamovie.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_HoF::restorePage3() { + screen()->copyBlockToPage(2, 0, 0, 320, 144, _gamePlayBuffer); +} + +void KyraEngine_HoF::clearAnimObjects() { + _animObjects[0].index = 0; + _animObjects[0].type = 0; + _animObjects[0].enabled = 1; + _animObjects[0].flags = 0x800; + _animObjects[0].width = 32; + _animObjects[0].height = 49; + _animObjects[0].width2 = 4; + _animObjects[0].height2 = 10; + + for (int i = 1; i < 11; ++i) { + _animObjects[i].index = i; + _animObjects[i].type = 2; + } + + for (int i = 11; i <= 40; ++i) { + _animObjects[i].index = i; + _animObjects[i].type = 1; + _animObjects[i].flags = 0x800; + _animObjects[i].width = 16; + _animObjects[i].height = 16; + } +} + +void KyraEngine_HoF::drawAnimObjects() { + for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { + if (!curObject->enabled) + continue; + + int x = curObject->xPos2 - (_screen->getScreenDim(2)->sx << 3); + int y = curObject->yPos2 - _screen->getScreenDim(2)->sy; + int layer = 7; + + if (curObject->flags & 0x800) { + if (curObject->animFlags) + layer = 0; + else + layer = getDrawLayer(curObject->xPos1, curObject->yPos1); + } + curObject->flags |= 0x800; + + if (curObject->index) + drawSceneAnimObject(curObject, x, y, layer); + else + drawCharacterAnimObject(curObject, x, y, layer); + } +} + +void KyraEngine_HoF::refreshAnimObjects(int force) { + for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { + if (!curObject->enabled) + continue; + if (!curObject->needRefresh && !force) + continue; + + int x = curObject->xPos2 - curObject->width2; + if (x < 0) + x = 0; + if (x >= 320) + x = 319; + int y = curObject->yPos2 - curObject->height2; + if (y < 0) + y = 0; + if (y >= 143) + y = 142; + + int width = curObject->width + curObject->width2 + 8; + int height = curObject->height + curObject->height2*2; + if (width + x > 320) + width -= width + x - 322; + if (height + y > 143) + height -= height + y - 144; + + _screen->copyRegion(x, y, x, y, width, height, 2, 0, Screen::CR_NO_P_CHECK); + + curObject->needRefresh = false; + } +} + +void KyraEngine_HoF::updateItemAnimations() { + bool nextFrame = false; + + if (_itemAnimDefinition[0].itemIndex == -1 || _inventorySaved) + return; + + const ItemAnimDefinition *s = &_itemAnimDefinition[_nextAnimItem]; + ActiveItemAnim *a = &_activeItemAnim[_nextAnimItem]; + _nextAnimItem = (_nextAnimItem + 1) % _itemAnimDefinitionSize; + + if (_system->getMillis() < a->nextFrameTime) + return; + + uint16 shpIdx = s->frames[a->currentFrame].index + 64; + if (s->itemIndex == _mouseState && s->itemIndex == _itemInHand && _screen->isMouseVisible()) { + nextFrame = true; + _screen->setMouseCursor(8, 15, getShapePtr(shpIdx)); + } + + for (int i = 0; i < 10; i++) { + if (s->itemIndex == _mainCharacter.inventory[i]) { + nextFrame = true; + _screen->drawShape(2, getShapePtr(240 + i), 304, 184, 0, 0); + _screen->drawShape(2, getShapePtr(shpIdx), 304, 184, 0, 0); + _screen->copyRegion(304, 184, _inventoryX[i], _inventoryY[i], 16, 16, 2, 0); + } + } + + _screen->updateScreen(); + + for (int i = 11; i < 40; i++) { + AnimObj *animObject = &_animObjects[i]; + if (animObject->shapeIndex2 == s->itemIndex + 64) { + if (s->itemIndex == 121) { + int f = findItem(_mainCharacter.sceneId, 121); + int nx = _itemList[f].x - 4; + if (nx > 12) { + if (lineIsPassable(nx, _itemList[f].y)) { + animObject->xPos2 -= 4; + _itemList[f].x -= 4; + } + } + } + animObject->shapePtr = getShapePtr(shpIdx); + animObject->shapeIndex1 = shpIdx; + animObject->needRefresh = 1; + nextFrame = true; + } + } + + if (nextFrame) { + a->nextFrameTime = _system->getMillis() + (s->frames[a->currentFrame].delay * _tickLength); + a->currentFrame = (a->currentFrame + 1) % s->numFrames; + } +} + +void KyraEngine_HoF::updateCharFacing() { + if (_mainCharacter.x1 > _mouseX) + _mainCharacter.facing = 5; + else + _mainCharacter.facing = 3; + + _mainCharacter.animFrame = _characterFrameTable[_mainCharacter.facing]; + updateCharacterAnim(0); + refreshAnimObjectsIfNeed(); +} + +void KyraEngine_HoF::updateCharacterAnim(int) { + Character *c = &_mainCharacter; + AnimObj *animState = _animObjects; + + animState->needRefresh = 1; + animState->specialRefresh = 1; + + if (c->facing >= 1 && c->facing <= 3) + animState->flags |= 1; + else if (c->facing >= 5 && c->facing <= 7) + animState->flags &= ~1; + + animState->xPos2 = animState->xPos1 = c->x1; + animState->yPos2 = animState->yPos1 = c->y1; + animState->shapePtr = getShapePtr(c->animFrame); + animState->shapeIndex1 = animState->shapeIndex2 = c->animFrame; + + int xAdd = _shapeDescTable[c->animFrame-9].xAdd; + int yAdd = _shapeDescTable[c->animFrame-9].yAdd; + + _charScale = getScale(c->x1, c->y1); + + animState->xPos2 += (xAdd * _charScale) >> 8; + animState->yPos2 += (yAdd * _charScale) >> 8; + animState->width2 = 8; + animState->height2 = 10; + + _animList = deleteAnimListEntry(_animList, animState); + if (_animList) + _animList = addToAnimListSorted(_animList, animState); + else + _animList = initAnimList(_animList, animState); + + updateCharPal(1); +} + +void KyraEngine_HoF::updateSceneAnim(int anim, int newFrame) { + AnimObj *animObject = &_animObjects[1+anim]; + if (!animObject->enabled) + return; + + animObject->needRefresh = 1; + animObject->specialRefresh = 1; + animObject->flags = 0; + + if (_sceneAnims[anim].flags & 2) + animObject->flags |= 0x800; + else + animObject->flags &= ~0x800; + + if (_sceneAnims[anim].flags & 4) + animObject->flags |= 1; + else + animObject->flags &= ~1; + + if (_sceneAnims[anim].flags & 0x20) { + animObject->shapePtr = _sceneShapeTable[newFrame]; + animObject->shapeIndex2 = 0xFFFF; + animObject->shapeIndex3 = 0xFFFF; + animObject->animNum = 0xFFFF; + } else { + animObject->shapePtr = 0; + animObject->shapeIndex3 = newFrame; + animObject->animNum = anim; + } + + animObject->xPos1 = _sceneAnims[anim].x; + animObject->yPos1 = _sceneAnims[anim].y; + animObject->xPos2 = _sceneAnims[anim].x2; + animObject->yPos2 = _sceneAnims[anim].y2; + + if (_sceneAnims[anim].flags & 2) { + _animList = deleteAnimListEntry(_animList, animObject); + if (!_animList) + _animList = initAnimList(_animList, animObject); + else + _animList = addToAnimListSorted(_animList, animObject); + } +} + +void KyraEngine_HoF::drawSceneAnimObject(AnimObj *obj, int x, int y, int layer) { + if (obj->type == 1) { + if (obj->shapeIndex1 == 0xFFFF) + return; + int scale = getScale(obj->xPos1, obj->yPos1); + _screen->drawShape(2, getShapePtr(obj->shapeIndex1), x, y, 2, obj->flags | 4, layer, scale, scale); + return; + } + + if (obj->shapePtr) { + _screen->drawShape(2, obj->shapePtr, x, y, 2, obj->flags, layer); + } else { + if (obj->shapeIndex3 == 0xFFFF || obj->animNum == 0xFFFF) + return; + + int flags = 0x4000; + if (obj->flags & 0x800) + flags |= 0x8000; + + if (_sceneAnims[obj->animNum].wsaFlag) { + x = y = 0; + } else { + x = obj->xPos2; + y = obj->yPos2; + } + + _sceneAnimMovie[obj->animNum]->displayFrame(obj->shapeIndex3, 2, x, y, int(flags | layer), 0, 0); + } +} + +void KyraEngine_HoF::drawCharacterAnimObject(AnimObj *obj, int x, int y, int layer) { + if (_drawNoShapeFlag || obj->shapeIndex1 == 0xFFFF) + return; + _screen->drawShape(2, getShapePtr(obj->shapeIndex1), x, y, 2, obj->flags | 4, layer, _charScale, _charScale); +} + +void KyraEngine_HoF::setCharacterAnimDim(int w, int h) { + restorePage3(); + + _animObj0Width = _animObjects[0].width; + _animObj0Height = _animObjects[0].height; + + _animObjects[0].width = w; + _animObjects[0].height = h; +} + +void KyraEngine_HoF::resetCharacterAnimDim() { + restorePage3(); + + _animObjects[0].width = _animObj0Width; + _animObjects[0].height = _animObj0Height; +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/animator_lok.cpp b/engines/kyra/graphics/animator_lok.cpp new file mode 100644 index 0000000000..9f4236b32b --- /dev/null +++ b/engines/kyra/graphics/animator_lok.cpp @@ -0,0 +1,651 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_lok.h" +#include "kyra/graphics/screen.h" +#include "kyra/graphics/animator_lok.h" +#include "kyra/engine/sprites.h" + +namespace Kyra { + +Animator_LoK::Animator_LoK(KyraEngine_LoK *vm, OSystem *system) { + _vm = vm; + _screen = vm->screen(); + _initOk = false; + _system = system; + _screenObjects = _actors = _items = _sprites = _objectQueue = 0; + _noDrawShapesFlag = 0; + + _actorBkgBackUp[0] = new uint8[_screen->getRectSize(8, 69)]; + memset(_actorBkgBackUp[0], 0, _screen->getRectSize(8, 69)); + _actorBkgBackUp[1] = new uint8[_screen->getRectSize(8, 69)]; + memset(_actorBkgBackUp[1], 0, _screen->getRectSize(8, 69)); +} + +Animator_LoK::~Animator_LoK() { + close(); + delete[] _actorBkgBackUp[0]; + delete[] _actorBkgBackUp[1]; +} + +void Animator_LoK::init(int actors_, int items_, int sprites_) { + _screenObjects = new AnimObject[actors_ + items_ + sprites_]; + assert(_screenObjects); + memset(_screenObjects, 0, sizeof(AnimObject) * (actors_ + items_ + sprites_)); + _actors = _screenObjects; + _sprites = &_screenObjects[actors_]; + _items = &_screenObjects[actors_ + items_]; + _brandonDrawFrame = 113; + + _initOk = true; +} + +void Animator_LoK::close() { + if (_initOk) { + _initOk = false; + delete[] _screenObjects; + _screenObjects = _actors = _items = _sprites = _objectQueue = 0; + } +} + +void Animator_LoK::initAnimStateList() { + AnimObject *animStates = _screenObjects; + animStates[0].index = 0; + animStates[0].active = 1; + animStates[0].flags = 0x800; + animStates[0].background = _actorBkgBackUp[0]; + animStates[0].rectSize = _screen->getRectSize(4, 48); + animStates[0].width = 4; + animStates[0].height = 48; + animStates[0].width2 = 4; + animStates[0].height2 = 3; + + for (int i = 1; i <= 4; ++i) { + animStates[i].index = i; + animStates[i].active = 0; + animStates[i].flags = 0x800; + animStates[i].background = _actorBkgBackUp[1]; + animStates[i].rectSize = _screen->getRectSize(4, 64); + animStates[i].width = 4; + animStates[i].height = 48; + animStates[i].width2 = 4; + animStates[i].height2 = 3; + } + + for (int i = 5; i < 16; ++i) { + animStates[i].index = i; + animStates[i].active = 0; + animStates[i].flags = 0; + } + + for (int i = 16; i < 28; ++i) { + animStates[i].index = i; + animStates[i].flags = 0; + animStates[i].background = _vm->_shapes[345 + i]; + animStates[i].rectSize = _screen->getRectSize(3, 24); + animStates[i].width = 3; + animStates[i].height = 16; + animStates[i].width2 = 0; + animStates[i].height2 = 0; + } +} + +void Animator_LoK::preserveAllBackgrounds() { + uint8 curPage = _screen->_curPage; + _screen->_curPage = 2; + + AnimObject *curObject = _objectQueue; + while (curObject) { + if (curObject->active && !curObject->disable) { + preserveOrRestoreBackground(curObject, false); + curObject->bkgdChangeFlag = 0; + } + curObject = curObject->nextAnimObject; + } + _screen->_curPage = curPage; +} + +void Animator_LoK::flagAllObjectsForBkgdChange() { + AnimObject *curObject = _objectQueue; + while (curObject) { + curObject->bkgdChangeFlag = 1; + curObject = curObject->nextAnimObject; + } +} + +void Animator_LoK::flagAllObjectsForRefresh() { + AnimObject *curObject = _objectQueue; + while (curObject) { + curObject->refreshFlag = 1; + curObject = curObject->nextAnimObject; + } +} + +void Animator_LoK::restoreAllObjectBackgrounds() { + AnimObject *curObject = _objectQueue; + _screen->_curPage = 2; + + while (curObject) { + if (curObject->active && !curObject->disable) { + preserveOrRestoreBackground(curObject, true); + curObject->x2 = curObject->x1; + curObject->y2 = curObject->y1; + } + curObject = curObject->nextAnimObject; + } + + _screen->_curPage = 0; +} + +void Animator_LoK::preserveAnyChangedBackgrounds() { + AnimObject *curObject = _objectQueue; + _screen->_curPage = 2; + + while (curObject) { + if (curObject->active && !curObject->disable && curObject->bkgdChangeFlag) { + preserveOrRestoreBackground(curObject, false); + curObject->bkgdChangeFlag = 0; + } + curObject = curObject->nextAnimObject; + } + + _screen->_curPage = 0; +} + +void Animator_LoK::preserveOrRestoreBackground(AnimObject *obj, bool restore) { + int x = 0, y = 0, width = obj->width, height = obj->height; + + if (restore) { + x = obj->x2 >> 3; + y = obj->y2; + } else { + x = obj->x1 >> 3; + y = obj->y1; + } + + if (x < 0) + x = 0; + if (y < 0) + y = 0; + + int temp; + + temp = x + width; + if (temp >= 39) + x = 39 - width; + temp = y + height; + if (temp >= 136) + y = 136 - height; + + if (restore) + _screen->copyBlockToPage(_screen->_curPage, x << 3, y, width << 3, height, obj->background); + else + _screen->copyRegionToBuffer(_screen->_curPage, x << 3, y, width << 3, height, obj->background); +} + +void Animator_LoK::prepDrawAllObjects() { + AnimObject *curObject = _objectQueue; + int drawPage = 2; + int flagUnk1 = 0, flagUnk2 = 0, flagUnk3 = 0; + if (_noDrawShapesFlag) + return; + if (_vm->_brandonStatusBit & 0x20) + flagUnk1 = 0x200; + if (_vm->_brandonStatusBit & 0x40) + flagUnk2 = 0x4000; + + while (curObject) { + if (curObject->active) { + int xpos = curObject->x1; + int ypos = curObject->y1; + + int drawLayer = 0; + if (!(curObject->flags & 0x800)) + drawLayer = 7; + else if (curObject->disable) + drawLayer = 0; + else + drawLayer = _vm->_sprites->getDrawLayer(curObject->drawY); + + // talking head functionallity + if (_vm->_talkingCharNum != -1 && (_vm->_currentCharacter->currentAnimFrame != 88 || curObject->index != 0)) { + const int16 baseAnimFrameTable1[] = { 0x11, 0x35, 0x59, 0x00, 0x00, 0x00 }; + const int16 baseAnimFrameTable2[] = { 0x15, 0x39, 0x5D, 0x00, 0x00, 0x00 }; + const int8 xOffsetTable1[] = { 2, 4, 0, 5, 2, 0, 0, 0 }; + const int8 xOffsetTable2[] = { 6, 4, 8, 3, 6, 0, 0, 0 }; + const int8 yOffsetTable1[] = { 0, 8, 1, 1, 0, 0, 0, 0 }; + const int8 yOffsetTable2[] = { 0, 8, 1, 1, 0, 0, 0, 0 }; + if (curObject->index == 0 || curObject->index <= 4) { + int shapesIndex = 0; + if (curObject->index == _vm->_charSayUnk3) { + shapesIndex = _vm->_currHeadShape + baseAnimFrameTable1[curObject->index]; + } else { + shapesIndex = baseAnimFrameTable2[curObject->index]; + int temp2 = 0; + if (curObject->index == 2) { + if (_vm->_characterList[2].sceneId == 77 || _vm->_characterList[2].sceneId == 86) + temp2 = 1; + else + temp2 = 0; + } else { + temp2 = 1; + } + + if (!temp2) + shapesIndex = -1; + } + + xpos = curObject->x1; + ypos = curObject->y1; + + int tempX = 0, tempY = 0; + if (curObject->flags & 0x1) { + tempX = (xOffsetTable1[curObject->index] * _brandonScaleX) >> 8; + tempY = yOffsetTable1[curObject->index]; + } else { + tempX = (xOffsetTable2[curObject->index] * _brandonScaleX) >> 8; + tempY = yOffsetTable2[curObject->index]; + } + tempY = (tempY * _brandonScaleY) >> 8; + xpos += tempX; + ypos += tempY; + + if (_vm->_scaleMode && _brandonScaleX != 256) + ++xpos; + + if (curObject->index == 0 && shapesIndex != -1) { + if (!(_vm->_brandonStatusBit & 2)) { + flagUnk3 = 0x100; + if ((flagUnk1 & 0x200) || (flagUnk2 & 0x4000)) + flagUnk3 = 0; + + int tempFlags = 0; + if (flagUnk3 & 0x100) { + tempFlags = curObject->flags & 1; + tempFlags |= 0x800 | flagUnk1 | 0x100; + } + + if (!(flagUnk3 & 0x100) && (flagUnk2 & 0x4000)) { + tempFlags = curObject->flags & 1; + tempFlags |= 0x900 | flagUnk1 | 0x4000; + _screen->drawShape(drawPage, _vm->_shapes[shapesIndex], xpos, ypos, 2, tempFlags | 4, _vm->_brandonPoisonFlagsGFX, int(1), int(_vm->_brandonInvFlag), drawLayer, _brandonScaleX, _brandonScaleY); + } else { + if (!(flagUnk2 & 0x4000)) { + tempFlags = curObject->flags & 1; + tempFlags |= 0x900 | flagUnk1; + } + + _screen->drawShape(drawPage, _vm->_shapes[shapesIndex], xpos, ypos, 2, tempFlags | 4, _vm->_brandonPoisonFlagsGFX, int(1), drawLayer, _brandonScaleX, _brandonScaleY); + } + } + } else { + if (shapesIndex != -1) { + int tempFlags = 0; + if (curObject->flags & 1) + tempFlags = 1; + _screen->drawShape(drawPage, _vm->_shapes[shapesIndex], xpos, ypos, 2, tempFlags | 0x800, drawLayer); + } + } + } + } + + xpos = curObject->x1; + ypos = curObject->y1; + + curObject->flags |= 0x800; + if (curObject->index == 0) { + flagUnk3 = 0x100; + + if (flagUnk1 & 0x200 || flagUnk2 & 0x4000) + flagUnk3 = 0; + + if (_vm->_brandonStatusBit & 2) + curObject->flags &= 0xFFFFFFFE; + + if (!_vm->_scaleMode) { + if (flagUnk3 & 0x100) + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x100, (uint8 *)_vm->_brandonPoisonFlagsGFX, int(1), drawLayer); + else if (flagUnk2 & 0x4000) + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x4000, int(_vm->_brandonInvFlag), drawLayer); + else + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1, drawLayer); + } else { + if (flagUnk3 & 0x100) + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x104, (uint8 *)_vm->_brandonPoisonFlagsGFX, int(1), drawLayer, _brandonScaleX, _brandonScaleY); + else if (flagUnk2 & 0x4000) + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x4004, int(_vm->_brandonInvFlag), drawLayer, _brandonScaleX, _brandonScaleY); + else + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | flagUnk1 | 0x4, drawLayer, _brandonScaleX, _brandonScaleY); + } + } else { + if (curObject->index >= 16 && curObject->index <= 27) + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags | 4, drawLayer, (int)_vm->_scaleTable[curObject->drawY], (int)_vm->_scaleTable[curObject->drawY]); + else + _screen->drawShape(drawPage, curObject->sceneAnimPtr, xpos, ypos, 2, curObject->flags, drawLayer); + } + } + curObject = curObject->nextAnimObject; + } +} + +void Animator_LoK::copyChangedObjectsForward(int refreshFlag) { + for (AnimObject *curObject = _objectQueue; curObject; curObject = curObject->nextAnimObject) { + if (curObject->active) { + if (curObject->refreshFlag || refreshFlag) { + int xpos = 0, ypos = 0, width = 0, height = 0; + xpos = (curObject->x1 >> 3) - (curObject->width2 >> 3) - 1; + ypos = curObject->y1 - curObject->height2; + width = curObject->width + (curObject->width2 >> 3) + 2; + height = curObject->height + curObject->height2 * 2; + + if (xpos < 1) + xpos = 1; + else if (xpos > 39) + continue; + + if (xpos + width > 39) + width = 39 - xpos; + + if (ypos < 8) + ypos = 8; + else if (ypos > 136) + continue; + + if (ypos + height > 136) + height = 136 - ypos; + + _screen->copyRegion(xpos << 3, ypos, xpos << 3, ypos, width << 3, height, 2, 0); + curObject->refreshFlag = 0; + } + } + } + + _screen->updateScreen(); +} + +void Animator_LoK::updateAllObjectShapes() { + restoreAllObjectBackgrounds(); + preserveAnyChangedBackgrounds(); + prepDrawAllObjects(); + copyChangedObjectsForward(0); +} + +void Animator_LoK::animRemoveGameItem(int index) { + restoreAllObjectBackgrounds(); + + AnimObject *animObj = &_items[index]; + animObj->sceneAnimPtr = 0; + animObj->animFrameNumber = -1; + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; + updateAllObjectShapes(); + animObj->active = 0; + + objectRemoveQueue(_objectQueue, animObj); +} + +void Animator_LoK::animAddGameItem(int index, uint16 sceneId) { + restoreAllObjectBackgrounds(); + assert(sceneId < _vm->_roomTableSize); + Room *currentRoom = &_vm->_roomTable[sceneId]; + AnimObject *animObj = &_items[index]; + animObj->active = 1; + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; + animObj->drawY = currentRoom->itemsYPos[index]; + animObj->sceneAnimPtr = _vm->_shapes[216 + currentRoom->itemsTable[index]]; + animObj->animFrameNumber = -1; + animObj->x1 = currentRoom->itemsXPos[index]; + animObj->y1 = currentRoom->itemsYPos[index]; + animObj->x1 -= fetchAnimWidth(animObj->sceneAnimPtr, _vm->_scaleTable[animObj->drawY]) >> 1; + animObj->y1 -= fetchAnimHeight(animObj->sceneAnimPtr, _vm->_scaleTable[animObj->drawY]); + animObj->x2 = animObj->x1; + animObj->y2 = animObj->y1; + animObj->width2 = 0; + animObj->height2 = 0; + _objectQueue = objectQueue(_objectQueue, animObj); + preserveAnyChangedBackgrounds(); + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; +} + +void Animator_LoK::animAddNPC(int character) { + restoreAllObjectBackgrounds(); + AnimObject *animObj = &_actors[character]; + const Character *ch = &_vm->_characterList[character]; + + animObj->active = 1; + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; + animObj->drawY = ch->y1; + animObj->sceneAnimPtr = _vm->_shapes[ch->currentAnimFrame]; + animObj->x1 = animObj->x2 = ch->x1 + _vm->_defaultShapeTable[ch->currentAnimFrame - 7].xOffset; + animObj->y1 = animObj->y2 = ch->y1 + _vm->_defaultShapeTable[ch->currentAnimFrame - 7].yOffset; + + if (ch->facing >= 1 && ch->facing <= 3) + animObj->flags |= 1; + else if (ch->facing >= 5 && ch->facing <= 7) + animObj->flags &= 0xFFFFFFFE; + + _objectQueue = objectQueue(_objectQueue, animObj); + preserveAnyChangedBackgrounds(); + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; +} + +Animator_LoK::AnimObject *Animator_LoK::objectRemoveQueue(AnimObject *queue, AnimObject *rem) { + AnimObject *cur = queue; + AnimObject *prev = queue; + + while (cur != rem && cur) { + AnimObject *temp = cur->nextAnimObject; + if (!temp) + break; + prev = cur; + cur = temp; + } + + if (cur == queue) { + if (!cur) + return 0; + return cur->nextAnimObject; + } + + if (!cur->nextAnimObject) { + if (cur == rem) { + if (!prev) + return 0; + else + prev->nextAnimObject = 0; + } + } else { + if (cur == rem) + prev->nextAnimObject = rem->nextAnimObject; + } + + return queue; +} + +Animator_LoK::AnimObject *Animator_LoK::objectAddHead(AnimObject *queue, AnimObject *head) { + head->nextAnimObject = queue; + return head; +} + +Animator_LoK::AnimObject *Animator_LoK::objectQueue(AnimObject *queue, AnimObject *add) { + if (!queue || add->drawY <= queue->drawY) { + add->nextAnimObject = queue; + return add; + } + AnimObject *cur = queue; + AnimObject *prev = queue; + while (add->drawY > cur->drawY) { + AnimObject *temp = cur->nextAnimObject; + if (!temp) + break; + prev = cur; + cur = temp; + } + + if (add->drawY <= cur->drawY) { + prev->nextAnimObject = add; + add->nextAnimObject = cur; + } else { + cur->nextAnimObject = add; + add->nextAnimObject = 0; + } + return queue; +} + +void Animator_LoK::addObjectToQueue(AnimObject *object) { + if (!_objectQueue) + _objectQueue = objectAddHead(0, object); + else + _objectQueue = objectQueue(_objectQueue, object); +} + +void Animator_LoK::refreshObject(AnimObject *object) { + _objectQueue = objectRemoveQueue(_objectQueue, object); + if (_objectQueue) + _objectQueue = objectQueue(_objectQueue, object); + else + _objectQueue = objectAddHead(0, object); +} + +void Animator_LoK::makeBrandonFaceMouse() { + Common::Point mouse = _vm->getMousePos(); + if (mouse.x >= _vm->_currentCharacter->x1) + _vm->_currentCharacter->facing = 3; + else + _vm->_currentCharacter->facing = 5; + animRefreshNPC(0); + updateAllObjectShapes(); +} + +int16 Animator_LoK::fetchAnimWidth(const uint8 *shape, int16 mult) { + if (_vm->gameFlags().useAltShapeHeader) + shape += 2; + return (((int16)READ_LE_UINT16((shape + 3))) * mult) >> 8; +} + +int16 Animator_LoK::fetchAnimHeight(const uint8 *shape, int16 mult) { + if (_vm->gameFlags().useAltShapeHeader) + shape += 2; + return (int16)(((int8)*(shape + 2)) * mult) >> 8; +} + +void Animator_LoK::setBrandonAnimSeqSize(int width, int height) { + restoreAllObjectBackgrounds(); + _brandonAnimSeqSizeWidth = _actors[0].width; + _brandonAnimSeqSizeHeight = _actors[0].height; + _actors[0].width = width + 1; + _actors[0].height = height; + preserveAllBackgrounds(); +} + +void Animator_LoK::resetBrandonAnimSeqSize() { + restoreAllObjectBackgrounds(); + _actors[0].width = _brandonAnimSeqSizeWidth; + _actors[0].height = _brandonAnimSeqSizeHeight; + preserveAllBackgrounds(); +} + +void Animator_LoK::animRefreshNPC(int character) { + AnimObject *animObj = &_actors[character]; + Character *ch = &_vm->characterList()[character]; + + animObj->refreshFlag = 1; + animObj->bkgdChangeFlag = 1; + int facing = ch->facing; + if (facing >= 1 && facing <= 3) + animObj->flags |= 1; + else if (facing >= 5 && facing <= 7) + animObj->flags &= 0xFFFFFFFE; + + animObj->drawY = ch->y1; + animObj->sceneAnimPtr = _vm->shapes()[ch->currentAnimFrame]; + animObj->animFrameNumber = ch->currentAnimFrame; + if (character == 0) { + if (_vm->brandonStatus() & 10) { + animObj->animFrameNumber = 88; + ch->currentAnimFrame = 88; + } + if (_vm->brandonStatus() & 2) { + animObj->animFrameNumber = _brandonDrawFrame; + ch->currentAnimFrame = _brandonDrawFrame; + animObj->sceneAnimPtr = _vm->shapes()[_brandonDrawFrame]; + if (_vm->_brandonStatusBit0x02Flag) { + ++_brandonDrawFrame; + // TODO: check this + if (_brandonDrawFrame >= 122) { + _brandonDrawFrame = 113; + _vm->_brandonStatusBit0x02Flag = 0; + } + } + } + } + + int xOffset = _vm->_defaultShapeTable[ch->currentAnimFrame - 7].xOffset; + int yOffset = _vm->_defaultShapeTable[ch->currentAnimFrame - 7].yOffset; + + if (_vm->_scaleMode) { + animObj->x1 = ch->x1; + animObj->y1 = ch->y1; + + int newScale = _vm->_scaleTable[ch->y1]; + _brandonScaleX = newScale; + _brandonScaleY = newScale; + + animObj->x1 += (_brandonScaleX * xOffset) >> 8; + animObj->y1 += (_brandonScaleY * yOffset) >> 8; + } else { + animObj->x1 = ch->x1 + xOffset; + animObj->y1 = ch->y1 + yOffset; + } + animObj->width2 = 4; + animObj->height2 = 3; + + refreshObject(animObj); +} + +void Animator_LoK::setCharacterDefaultFrame(int character) { + static const uint16 initFrameTable[] = { + 7, 41, 77, 0, 0 + }; + assert(character < ARRAYSIZE(initFrameTable)); + Character *edit = &_vm->characterList()[character]; + edit->sceneId = 0xFFFF; + edit->facing = 0; + edit->currentAnimFrame = initFrameTable[character]; + // edit->unk6 = 1; +} + +void Animator_LoK::setCharactersHeight() { + static const int8 initHeightTable[] = { + 48, 40, 48, 47, 56, + 44, 42, 47, 38, 35, + 40 + }; + for (int i = 0; i < 11; ++i) + _vm->characterList()[i].height = initHeightTable[i]; +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/animator_lok.h b/engines/kyra/graphics/animator_lok.h new file mode 100644 index 0000000000..55c4d571fd --- /dev/null +++ b/engines/kyra/graphics/animator_lok.h @@ -0,0 +1,127 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_ANIMATOR_LOK_H +#define KYRA_ANIMATOR_LOK_H + +namespace Kyra { +class KyraEngine_LoK; +class Screen; + +class Animator_LoK { +public: + struct AnimObject { + uint8 index; + uint32 active; + uint32 refreshFlag; + uint32 bkgdChangeFlag; + bool disable; + uint32 flags; + int16 drawY; + uint8 *sceneAnimPtr; + int16 animFrameNumber; + uint8 *background; + uint16 rectSize; + int16 x1, y1; + int16 x2, y2; + uint16 width; + uint16 height; + uint16 width2; + uint16 height2; + AnimObject *nextAnimObject; + }; + + Animator_LoK(KyraEngine_LoK *vm, OSystem *system); + virtual ~Animator_LoK(); + + operator bool() const { return _initOk; } + + void init(int actors, int items, int sprites); + void close(); + + AnimObject *objects() { return _screenObjects; } + AnimObject *actors() { return _actors; } + AnimObject *items() { return _items; } + AnimObject *sprites() { return _sprites; } + + void initAnimStateList(); + void preserveAllBackgrounds(); + void flagAllObjectsForBkgdChange(); + void flagAllObjectsForRefresh(); + void restoreAllObjectBackgrounds(); + void preserveAnyChangedBackgrounds(); + virtual void prepDrawAllObjects(); + void copyChangedObjectsForward(int refreshFlag); + + void updateAllObjectShapes(); + void animRemoveGameItem(int index); + void animAddGameItem(int index, uint16 sceneId); + void animAddNPC(int character); + void animRefreshNPC(int character); + + void clearQueue() { _objectQueue = 0; } + void addObjectToQueue(AnimObject *object); + void refreshObject(AnimObject *object); + + void makeBrandonFaceMouse(); + void setBrandonAnimSeqSize(int width, int height); + void resetBrandonAnimSeqSize(); + void setCharacterDefaultFrame(int character); + void setCharactersHeight(); + + int16 fetchAnimWidth(const uint8 *shape, int16 mult); + int16 fetchAnimHeight(const uint8 *shape, int16 mult); + + int _noDrawShapesFlag; + uint16 _brandonDrawFrame; + int _brandonScaleX; + int _brandonScaleY; + +protected: + KyraEngine_LoK *_vm; + Screen *_screen; + OSystem *_system; + bool _initOk; + + AnimObject *_screenObjects; + + AnimObject *_actors; + AnimObject *_items; + AnimObject *_sprites; + + uint8 *_actorBkgBackUp[2]; + + AnimObject *objectRemoveQueue(AnimObject *queue, AnimObject *rem); + AnimObject *objectAddHead(AnimObject *queue, AnimObject *head); + AnimObject *objectQueue(AnimObject *queue, AnimObject *add); + + void preserveOrRestoreBackground(AnimObject *obj, bool restore); + + AnimObject *_objectQueue; + + int _brandonAnimSeqSizeWidth; + int _brandonAnimSeqSizeHeight; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/animator_mr.cpp b/engines/kyra/graphics/animator_mr.cpp new file mode 100644 index 0000000000..11b1bcb45a --- /dev/null +++ b/engines/kyra/graphics/animator_mr.cpp @@ -0,0 +1,456 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_mr.h" +#include "kyra/resource/resource.h" +#include "kyra/graphics/wsamovie.h" + +#include "common/system.h" + +namespace Kyra { + +void KyraEngine_MR::restorePage3() { + screen()->copyBlockToPage(2, 0, 0, 320, 200, _gamePlayBuffer); +} + +void KyraEngine_MR::clearAnimObjects() { + for (int i = 0; i < 67; ++i) + _animObjects[i].enabled = false; + + _animObjects[0].index = 0; + _animObjects[0].type = 0; + _animObjects[0].enabled = true; + _animObjects[0].specialRefresh = 1; + _animObjects[0].flags = 0x800; + _animObjects[0].width = 57; + _animObjects[0].height = 91; + _animObjects[0].width2 = 4; + _animObjects[0].height2 = 10; + + for (int i = 1; i < 17; ++i) { + _animObjects[i].index = i; + _animObjects[i].type = 2; + _animObjects[i].flags = 0; + _animObjects[i].enabled = false; + _animObjects[i].needRefresh = 0; + _animObjects[i].specialRefresh = 1; + } + + for (int i = 17; i <= 66; ++i) { + _animObjects[i].index = i; + _animObjects[i].type = 1; + _animObjects[i].specialRefresh = 1; + _animObjects[i].flags = 0x800; + _animObjects[i].width = 24; + _animObjects[i].height = 20; + _animObjects[i].width2 = 0; + _animObjects[i].height2 = 0; + } +} + +void KyraEngine_MR::animSetupPaletteEntry(AnimObj *anim) { + int layer = _screen->getLayer(anim->xPos1, anim->yPos1) - 1; + int16 count = 0; + for (int i = 0; i < 3; ++i) + count += _sceneDatPalette[layer*3+i]; + count /= 3; + count *= -1; + count = MAX(0, MIN(count, 10)); + anim->palette = count / 3; +} + +void KyraEngine_MR::drawAnimObjects() { + for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { + if (!curObject->enabled) + continue; + + int x = curObject->xPos2 - (_screen->getScreenDim(2)->sx << 3); + int y = curObject->yPos2 - _screen->getScreenDim(2)->sy; + int layer = 7; + + if (curObject->flags & 0x800) { + if (!curObject->specialRefresh) + layer = 0; + else + layer = getDrawLayer(curObject->xPos1, curObject->yPos1); + } + + if (curObject->index) + drawSceneAnimObject(curObject, x, y, layer); + else + drawCharacterAnimObject(curObject, x, y, layer); + } +} + +void KyraEngine_MR::drawSceneAnimObject(AnimObj *obj, int x, int y, int layer) { + if (obj->type == 1) { + if (obj->shapeIndex1 == 0xFFFF) + return; + int scale = getScale(obj->xPos1, obj->yPos1); + _screen->drawShape(2, getShapePtr(obj->shapeIndex1), x, y, 2, obj->flags | 0x104, _paletteOverlay, obj->palette, layer, scale, scale); + } else { + if (obj->shapePtr) { + _screen->drawShape(2, obj->shapePtr, x, y, 2, obj->flags, 7); + } else { + if (obj->shapeIndex3 == 0xFFFF || obj->animNum == 0xFFFF) + return; + uint16 flags = 0x4000; + if (obj->flags & 0x800) + flags |= 0x8000; + x = obj->xPos2 - _sceneAnimMovie[obj->animNum]->xAdd(); + y = obj->yPos2 - _sceneAnimMovie[obj->animNum]->yAdd(); + _sceneAnimMovie[obj->animNum]->displayFrame(obj->shapeIndex3, 2, x, y, flags | layer, 0, 0); + } + } +} + +void KyraEngine_MR::drawCharacterAnimObject(AnimObj *obj, int x, int y, int layer) { + if (_drawNoShapeFlag) + return; + + if (_mainCharacter.animFrame < 9) + _mainCharacter.animFrame = 87; + + if (obj->shapeIndex1 == 0xFFFF || _mainCharacter.animFrame == 87) + return; + + _screen->drawShape(2, getShapePtr(421), _mainCharacter.x3, _mainCharacter.y3, 2, obj->flags | 0x304, _paletteOverlay, 3, layer, _charScale, _charScale); + uint8 *shape = getShapePtr(_mainCharacter.animFrame); + if (shape) + _screen->drawShape(2, shape, x, y, 2, obj->flags | 4, layer, _charScale, _charScale); +} + +void KyraEngine_MR::refreshAnimObjects(int force) { + for (AnimObj *curObject = _animList; curObject; curObject = curObject->nextObject) { + if (!curObject->enabled) + continue; + if (!curObject->needRefresh && !force) + continue; + + const int scale = (curObject->index == 0) ? _charScale : 0; + + int x = curObject->xPos2 - curObject->width2; + if (scale) + x -= (0x100 - scale) >> 4; + + if (x < 0) + x = 0; + if (x >= 320) + x = 319; + + int y = curObject->yPos2 - curObject->height2; + if (scale) + y -= (0x100 - scale) >> 3; + if (y < 0) + y = 0; + if (y >= 187) + y = 186; + + int width = curObject->width + curObject->width2 + 8; + int height = curObject->height + curObject->height2*2; + if (width + x > 320) + width -= width + x - 322; + + const int maxY = _inventoryState ? 143 : 187; + if (height + y > maxY) + height -= height + y - (maxY + 1); + + if (height > 0) { + _screen->copyRegion(x, y, x, y, width, height, 2, 0, Screen::CR_NO_P_CHECK); + } + + curObject->needRefresh = false; + } +} + +void KyraEngine_MR::updateItemAnimations() { + bool nextFrame = false; + + if (_itemAnimDefinition[0].itemIndex == -1) + return; + + const ItemAnimDefinition *s = &_itemAnimDefinition[_nextAnimItem]; + ActiveItemAnim *a = &_activeItemAnim[_nextAnimItem]; + _nextAnimItem = (_nextAnimItem + 1) % 10; + + if (_system->getMillis() < a->nextFrameTime) + return; + + uint16 shpIdx = s->frames[a->currentFrame].index + 248; + if (s->itemIndex == _mouseState && s->itemIndex == _itemInHand && _screen->isMouseVisible()) { + nextFrame = true; + _screen->setMouseCursor(12, 19, getShapePtr(shpIdx)); + } + + if (_inventoryState) { + for (int i = 0; i < 10; i++) { + if (s->itemIndex == _mainCharacter.inventory[i]) { + nextFrame = true; + _screen->drawShape(2, getShapePtr(422 + i), 9, 0, 0, 0); + _screen->drawShape(2, getShapePtr(shpIdx), 9, 0, 0, 0); + _screen->copyRegion(9, 0, _inventoryX[i], _inventoryY[i], 24, 20, 2, 0, Screen::CR_NO_P_CHECK); + } + } + } + + _screen->updateScreen(); + + for (int i = 17; i < 66; i++) { + AnimObj *animObject = &_animObjects[i]; + if (animObject->shapeIndex2 == s->itemIndex + 248) { + animObject->shapePtr = getShapePtr(shpIdx); + animObject->shapeIndex1 = shpIdx; + animObject->needRefresh = true; + nextFrame = true; + } + } + + if (nextFrame) { + a->nextFrameTime = _system->getMillis() + (s->frames[a->currentFrame].delay * _tickLength); + a->currentFrame = (a->currentFrame + 1) % s->numFrames; + } +} + +void KyraEngine_MR::updateCharacterAnim(int charId) { + AnimObj *obj = &_animObjects[0]; + obj->needRefresh = true; + obj->flags &= ~1; + obj->xPos1 = _mainCharacter.x1; + obj->yPos1 = _mainCharacter.y1; + obj->shapePtr = getShapePtr(_mainCharacter.animFrame); + obj->shapeIndex1 = obj->shapeIndex2 = _mainCharacter.animFrame; + + int shapeOffsetX = 0, shapeOffsetY = 0; + if (_mainCharacter.animFrame >= 50 && _mainCharacter.animFrame <= 87) { + shapeOffsetX = _malcolmShapeXOffset; + shapeOffsetY = _malcolmShapeYOffset; + } else { + shapeOffsetX = _animShapeXAdd; + shapeOffsetY = _animShapeYAdd; + } + + obj->xPos2 = _mainCharacter.x1; + obj->yPos2 = _mainCharacter.y1; + _charScale = getScale(_mainCharacter.x1, _mainCharacter.y1); + obj->xPos2 += (shapeOffsetX * _charScale) >> 8; + obj->yPos2 += (shapeOffsetY * _charScale) >> 8; + _mainCharacter.x3 = _mainCharacter.x1 - (_charScale >> 4) - 1; + _mainCharacter.y3 = _mainCharacter.y1 - (_charScale >> 6) - 1; + if (_charBackUpWidth2 == -1) { + obj->width2 = 4; + obj->height2 = 10; + } + + for (int i = 1; i <= 16; ++i) { + if (_animObjects[i].enabled && _animObjects[i].specialRefresh) + _animObjects[i].needRefresh = true; + } + + _animList = deleteAnimListEntry(_animList, obj); + if (_animList) + _animList = addToAnimListSorted(_animList, obj); + else + _animList = initAnimList(_animList, obj); + + if (!_loadingState) + updateCharPal(1); +} + +void KyraEngine_MR::updateSceneAnim(int anim, int newFrame) { + AnimObj *animObject = &_animObjects[1+anim]; + if (!animObject->enabled) + return; + + animObject->needRefresh = true; + + if (_sceneAnims[anim].flags & 2) + animObject->flags |= 1; + else + animObject->flags &= ~1; + + if (_sceneAnims[anim].flags & 4) { + animObject->shapePtr = _sceneShapes[newFrame]; + animObject->shapeIndex2 = 0xFFFF; + animObject->shapeIndex3 = 0xFFFF; + animObject->animNum = 0xFFFF; + } else { + animObject->shapePtr = 0; + animObject->shapeIndex3 = newFrame; + animObject->animNum = anim; + } + + animObject->xPos1 = _sceneAnims[anim].x; + animObject->yPos1 = _sceneAnims[anim].y; + animObject->xPos2 = _sceneAnims[anim].x2; + animObject->yPos2 = _sceneAnims[anim].y2; + + if (_sceneAnims[anim].flags & 0x20) { + _animList = deleteAnimListEntry(_animList, animObject); + if (!_animList) + _animList = initAnimList(_animList, animObject); + else + _animList = addToAnimListSorted(_animList, animObject); + } +} + +void KyraEngine_MR::setupSceneAnimObject(int animId, uint16 flags, int x, int y, int x2, int y2, int w, + int h, int unk10, int specialSize, int unk14, int shape, const char *filename) { + restorePage3(); + SceneAnim &anim = _sceneAnims[animId]; + anim.flags = flags; + anim.x = x; + anim.y = y; + anim.x2 = x2; + anim.y2 = y2; + anim.width = w; + anim.height = h; + anim.specialSize = specialSize; + anim.shapeIndex = shape; + if (filename) + strcpy(anim.filename, filename); + + if (flags & 8) { + _sceneAnimMovie[animId]->open(filename, 1, 0); + if (_sceneAnimMovie[animId]->opened()) { + anim.wsaFlag = 1; + if (x2 == -1) + x2 = _sceneAnimMovie[animId]->xAdd(); + if (y2 == -1) + y2 = _sceneAnimMovie[animId]->yAdd(); + if (w == -1) + w = _sceneAnimMovie[animId]->width(); + if (h == -1) + h = _sceneAnimMovie[animId]->height(); + if (x == -1) + x = (w >> 1) + x2; + if (y == -1) + y = y2 + h - 1; + + anim.x = x; + anim.y = y; + anim.x2 = x2; + anim.y2 = y2; + anim.width = w; + anim.height = h; + } + } + + AnimObj *obj = &_animObjects[1+animId]; + obj->enabled = true; + obj->needRefresh = true; + + obj->specialRefresh = (anim.flags & 0x20) ? 1 : 0; + obj->flags = (anim.flags & 0x10) ? 0x800 : 0; + if (anim.flags & 2) + obj->flags |= 1; + + obj->xPos1 = anim.x; + obj->yPos1 = anim.y; + + if ((anim.flags & 4) && anim.shapeIndex != -1) + obj->shapePtr = _sceneShapes[anim.shapeIndex]; + else + obj->shapePtr = 0; + + if (anim.flags & 8) { + obj->shapeIndex3 = anim.shapeIndex; + obj->animNum = animId; + } else { + obj->shapeIndex3 = 0xFFFF; + obj->animNum = 0xFFFF; + } + + obj->xPos3 = obj->xPos2 = anim.x2; + obj->yPos3 = obj->yPos2 = anim.y2; + obj->width = anim.width; + obj->height = anim.height; + obj->width2 = obj->height2 = anim.specialSize; + + if (_animList) + _animList = addToAnimListSorted(_animList, obj); + else + _animList = initAnimList(_animList, obj); +} + +void KyraEngine_MR::removeSceneAnimObject(int anim, int refresh) { + AnimObj *obj = &_animObjects[anim+1]; + restorePage3(); + obj->shapeIndex3 = 0xFFFF; + obj->animNum = 0xFFFF; + obj->needRefresh = true; + + if (refresh) + refreshAnimObjectsIfNeed(); + + obj->enabled = false; + _animList = deleteAnimListEntry(_animList, obj); + _sceneAnimMovie[anim]->close(); +} + +void KyraEngine_MR::setCharacterAnimDim(int w, int h) { + restorePage3(); + _charBackUpWidth = _animObjects[0].width; + _charBackUpWidth2 = _animObjects[0].width2; + _charBackUpHeight = _animObjects[0].height; + _charBackUpHeight2 = _animObjects[0].height2; + + _animObjects[0].width2 = (w - _charBackUpWidth) / 2; + _animObjects[0].height2 = h - _charBackUpHeight; + _animObjects[0].width = w; + _animObjects[0].height = h; +} + +void KyraEngine_MR::resetCharacterAnimDim() { + restorePage3(); + _animObjects[0].width2 = _charBackUpWidth2; + _animObjects[0].height2 = _charBackUpHeight2; + _animObjects[0].width = _charBackUpWidth; + _animObjects[0].height = _charBackUpHeight; + _charBackUpWidth2 = _charBackUpHeight2 = -1; + _charBackUpWidth = _charBackUpHeight = -1; +} + +void KyraEngine_MR::showIdleAnim() { + if (_mainCharacter.sceneId == 20 || _mainCharacter.sceneId == 21 + || _mainCharacter.sceneId == 12 || _mainCharacter.sceneId == 11) + return; + + if (_mainCharacter.animFrame == 87) + return; + + if (!_nextIdleType && !talkObjectsInCurScene()) { + randomSceneChat(); + } else { + static const char *const facingTable[] = { + "A", "R", "R", "FR", "FX", "FL", "L", "L" + }; + + Common::String filename = Common::String::format( "MI0%s%.02d.EMC", facingTable[_mainCharacter.facing], _characterShapeFile); + + if (_res->exists(filename.c_str())) + runAnimationScript(filename.c_str(), 1, 1, 1, 1); + } + + _nextIdleType = !_nextIdleType; +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/animator_tim.cpp b/engines/kyra/graphics/animator_tim.cpp new file mode 100644 index 0000000000..160524e6ca --- /dev/null +++ b/engines/kyra/graphics/animator_tim.cpp @@ -0,0 +1,235 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/script/script_tim.h" +#include "kyra/graphics/wsamovie.h" +#include "kyra/graphics/screen_lol.h" + +#ifdef ENABLE_LOL +#include "kyra/engine/lol.h" +#else +#include "kyra/graphics/screen_v2.h" +#endif + +#include "common/system.h" + +namespace Kyra { + +#ifdef ENABLE_LOL +TimAnimator::TimAnimator(LoLEngine *engine, Screen_v2 *screen_v2, OSystem *system, bool useParts) : _vm(engine), _screen(screen_v2), _system(system), _useParts(useParts) { +#else +TimAnimator::TimAnimator(KyraEngine_v1 *engine, Screen_v2 *screen_v2, OSystem *system, bool useParts) : _vm(engine), _screen(screen_v2), _system(system), _useParts(useParts) { +#endif + _animations = new Animation[TIM::kWSASlots]; + memset(_animations, 0, TIM::kWSASlots * sizeof(Animation)); + + if (_useParts) { + for (int i = 0; i < TIM::kWSASlots; i++) { + _animations[i].parts = new AnimPart[TIM::kAnimParts]; + memset(_animations[i].parts, 0, TIM::kAnimParts * sizeof(AnimPart)); + } + } +} + +TimAnimator::~TimAnimator() { + for (int i = 0; i < TIM::kWSASlots; i++) { + delete _animations[i].wsa; + if (_useParts) + delete[] _animations[i].parts; + } + + delete[] _animations; +} + +void TimAnimator::init(int animIndex, Movie *wsa, int x, int y, int wsaCopyParams, int frameDelay) { + Animation *anim = &_animations[animIndex]; + anim->wsa = wsa; + anim->x = x; + anim->y = y; + anim->wsaCopyParams = wsaCopyParams; + anim->frameDelay = frameDelay; + anim->enable = 0; + anim->lastPart = -1; +} + +void TimAnimator::reset(int animIndex, bool clearStruct) { + Animation *anim = &_animations[animIndex]; + if (!anim) + return; + anim->field_D = 0; + anim->enable = 0; + delete anim->wsa; + anim->wsa = 0; + + if (clearStruct) { + if (_useParts) + delete[] anim->parts; + + memset(anim, 0, sizeof(Animation)); + + if (_useParts) { + anim->parts = new AnimPart[TIM::kAnimParts]; + memset(anim->parts, 0, TIM::kAnimParts * sizeof(AnimPart)); + } + } +} + +void TimAnimator::displayFrame(int animIndex, int page, int frame, int flags) { + Animation *anim = &_animations[animIndex]; + if ((anim->wsaCopyParams & 0x4000) != 0) + page = 2; + // WORKAROUND for some bugged scripts that will try to display frames of non-existent animations + if (anim->wsa) + anim->wsa->displayFrame(frame, page, anim->x, anim->y, (flags == -1) ? (anim->wsaCopyParams & 0xF0FF) : flags, 0, 0); + if (!page) + _screen->updateScreen(); +} + +#ifdef ENABLE_LOL +void TimAnimator::setupPart(int animIndex, int part, int firstFrame, int lastFrame, int cycles, int nextPart, int partDelay, int f, int sfxIndex, int sfxFrame) { + AnimPart *a = &_animations[animIndex].parts[part]; + a->firstFrame = firstFrame; + a->lastFrame = lastFrame; + a->cycles = cycles; + a->nextPart = nextPart; + a->partDelay = partDelay; + a->field_A = f; + a->sfxIndex = sfxIndex; + a->sfxFrame = sfxFrame; +} + +void TimAnimator::start(int animIndex, int part) { + if (!_vm || !_system || !_screen) + return; + + Animation *anim = &_animations[animIndex]; + anim->curPart = part; + AnimPart *p = &anim->parts[part]; + anim->enable = 1; + anim->nextFrame = _system->getMillis() + anim->frameDelay * _vm->_tickLength; + anim->curFrame = p->firstFrame; + anim->cyclesCompleted = 0; + + // WORKAROUND for some bugged scripts that will try to display frames of non-existent animations + if (anim->wsa) + anim->wsa->displayFrame(anim->curFrame - 1, 0, anim->x, anim->y, 0, 0, 0); +} + +void TimAnimator::stop(int animIndex) { + Animation *anim = &_animations[animIndex]; + anim->enable = 0; + anim->field_D = 0; + if (animIndex == 5) { + delete anim->wsa; + anim->wsa = 0; + } +} + +void TimAnimator::update(int animIndex) { + if (!_vm || !_system || !_screen) + return; + + Animation *anim = &_animations[animIndex]; + if (!anim->enable || anim->nextFrame >= _system->getMillis()) + return; + + AnimPart *p = &anim->parts[anim->curPart]; + anim->nextFrame = 0; + + int step = 0; + if (p->lastFrame >= p->firstFrame) { + step = 1; + anim->curFrame++; + } else { + step = -1; + anim->curFrame--; + } + + if (anim->curFrame == (p->lastFrame + step)) { + anim->cyclesCompleted++; + + if ((anim->cyclesCompleted > p->cycles) || anim->field_D) { + anim->lastPart = anim->curPart; + + if ((p->nextPart == -1) || (anim->field_D && p->field_A)) { + anim->enable = 0; + anim->field_D = 0; + return; + } + + anim->nextFrame += (p->partDelay * _vm->_tickLength); + anim->curPart = p->nextPart; + + p = &anim->parts[anim->curPart]; + anim->curFrame = p->firstFrame; + anim->cyclesCompleted = 0; + + } else { + anim->curFrame = p->firstFrame; + } + } + + if (p->sfxIndex != -1 && p->sfxFrame == anim->curFrame) + _vm->snd_playSoundEffect(p->sfxIndex, -1); + + anim->nextFrame += (anim->frameDelay * _vm->_tickLength); + + anim->wsa->displayFrame(anim->curFrame - 1, 0, anim->x, anim->y, 0, 0, 0); + anim->nextFrame += _system->getMillis(); +} + +void TimAnimator::playPart(int animIndex, int firstFrame, int lastFrame, int delay) { + if (!_vm || !_system || !_screen) + return; + + Animation *anim = &_animations[animIndex]; + // WORKAROUND for some bugged scripts that will try to play invalid animations + if (!anim->wsa) + return; + + int step = (lastFrame >= firstFrame) ? 1 : -1; + for (int i = firstFrame; i != (lastFrame + step); i += step) { + uint32 next = _system->getMillis() + delay * _vm->_tickLength; + if (anim->wsaCopyParams & 0x4000) { + _screen->copyRegion(112, 0, 112, 0, 176, 120, 6, 2); + anim->wsa->displayFrame(i - 1, 2, anim->x, anim->y, anim->wsaCopyParams & 0x1000 ? 0x5000 : 0x4000, _vm->_transparencyTable1, _vm->_transparencyTable2); + _screen->copyRegion(112, 0, 112, 0, 176, 120, 2, 0); + _screen->updateScreen(); + } else { + anim->wsa->displayFrame(i - 1, 0, anim->x, anim->y, 0, 0, 0); + _screen->updateScreen(); + } + int32 del = (int32)(next - _system->getMillis()); + if (del > 0) + _vm->delay(del, true); + } +} + +int TimAnimator::resetLastPart(int animIndex) { + Animation *anim = &_animations[animIndex]; + int8 res = -1; + SWAP(res, anim->lastPart); + return res; +} +#endif + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/animator_v2.cpp b/engines/kyra/graphics/animator_v2.cpp new file mode 100644 index 0000000000..a7a8d70962 --- /dev/null +++ b/engines/kyra/graphics/animator_v2.cpp @@ -0,0 +1,187 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/engine/kyra_v2.h" +#include "kyra/graphics/screen_v2.h" + +namespace Kyra { + +void KyraEngine_v2::allocAnimObjects(int actors, int anims, int items) { + _animObjects = new AnimObj[actors + anims + items]; + assert(_animObjects); + + memset(_animObjects, 0, sizeof(AnimObj) * (actors + anims + items)); + + _animActor = _animObjects; + _animAnims = _animObjects + actors; + _animItems = _animObjects + actors + anims; +} + +KyraEngine_v2::AnimObj *KyraEngine_v2::initAnimList(AnimObj *list, AnimObj *entry) { + entry->nextObject = list; + return entry; +} + +KyraEngine_v2::AnimObj *KyraEngine_v2::addToAnimListSorted(AnimObj *list, AnimObj *add) { + add->nextObject = 0; + + if (!list) + return add; + + if (add->yPos1 <= list->yPos1) { + add->nextObject = list; + return add; + } + + AnimObj *cur = list; + AnimObj *prev = list; + while (add->yPos1 > cur->yPos1) { + AnimObj *temp = cur->nextObject; + if (!temp) + break; + prev = cur; + cur = temp; + } + + if (add->yPos1 <= cur->yPos1) { + prev->nextObject = add; + add->nextObject = cur; + } else { + cur->nextObject = add; + add->nextObject = 0; + } + return list; +} + +KyraEngine_v2::AnimObj *KyraEngine_v2::deleteAnimListEntry(AnimObj *list, AnimObj *entry) { + if (!list) + return 0; + + AnimObj *old = 0; + AnimObj *cur = list; + + while (true) { + if (cur == entry) + break; + if (!cur->nextObject) + break; + old = cur; + cur = cur->nextObject; + } + + if (cur != entry) + return list; + + if (cur == list) { + if (!cur->nextObject) + return 0; + cur = cur->nextObject; + return cur; + } + + if (!cur->nextObject) { + if (!old) + return 0; + old->nextObject = 0; + return list; + } + + if (cur != entry) + return list; + + old->nextObject = entry->nextObject; + return list; +} + +void KyraEngine_v2::refreshAnimObjectsIfNeed() { + for (AnimObj *curEntry = _animList; curEntry; curEntry = curEntry->nextObject) { + if (curEntry->enabled && curEntry->needRefresh) { + restorePage3(); + drawAnimObjects(); + refreshAnimObjects(0); + screen()->updateScreen(); + return; + } + } +} + +void KyraEngine_v2::flagAnimObjsForRefresh() { + for (AnimObj *curEntry = _animList; curEntry; curEntry = curEntry->nextObject) + curEntry->needRefresh = 1; +} + +void KyraEngine_v2::flagAnimObjsSpecialRefresh() { + for (AnimObj *curEntry = _animList; curEntry; curEntry = curEntry->nextObject) + curEntry->specialRefresh = 1; +} + +void KyraEngine_v2::addItemToAnimList(int item) { + assert(item >= 0 && item < _itemListSize); + + restorePage3(); + + AnimObj *animObj = _animItems + item; + + animObj->enabled = 1; + animObj->needRefresh = 1; + + int itemId = _itemList[item].id; + + animObj->xPos2 = animObj->xPos1 = _itemList[item].x; + animObj->yPos2 = animObj->yPos1 = _itemList[item].y; + + animObj->shapePtr = getShapePtr(itemId + _desc.itemShapeStart); + animSetupPaletteEntry(animObj); + animObj->shapeIndex2 = animObj->shapeIndex1 = itemId + _desc.itemShapeStart; + + int scaleY, scaleX; + scaleY = scaleX = getScale(animObj->xPos1, animObj->yPos1); + + uint8 *shapePtr = getShapePtr(itemId + _desc.itemShapeStart); + animObj->xPos3 = (animObj->xPos2 -= (screen_v2()->getShapeScaledWidth(shapePtr, scaleX) >> 1)); + animObj->yPos3 = (animObj->yPos2 -= screen_v2()->getShapeScaledHeight(shapePtr, scaleY)); + + animObj->width2 = animObj->height2 = 0; + + _animList = addToAnimListSorted(_animList, animObj); + animObj->needRefresh = 1; +} + +void KyraEngine_v2::deleteItemAnimEntry(int item) { + assert(item < _itemListSize); + + AnimObj *animObj = _animItems + item; + + restorePage3(); + + animObj->shapePtr = 0; + animObj->shapeIndex1 = 0xFFFF; + animObj->shapeIndex2 = 0xFFFF; + animObj->needRefresh = 1; + + refreshAnimObjectsIfNeed(); + + animObj->enabled = 0; + _animList = deleteAnimListEntry(_animList, animObj); +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen.cpp b/engines/kyra/graphics/screen.cpp new file mode 100644 index 0000000000..a07e437d5f --- /dev/null +++ b/engines/kyra/graphics/screen.cpp @@ -0,0 +1,3971 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/screen.h" +#include "kyra/kyra_v1.h" +#include "kyra/resource/resource.h" + +#include "common/endian.h" +#include "common/memstream.h" +#include "common/system.h" +#include "common/config-manager.h" + +#include "engines/util.h" + +#include "graphics/cursorman.h" +#include "graphics/palette.h" +#include "graphics/sjis.h" + +namespace Kyra { + +Screen::Screen(KyraEngine_v1 *vm, OSystem *system, const ScreenDim *dimTable, const int dimTableSize) + : _system(system), _vm(vm), _sjisInvisibleColor(0), _dimTable(dimTable), _dimTableCount(dimTableSize), + _cursorColorKey((vm->game() == GI_KYRA1 || vm->game() == GI_EOB1 || vm->game() == GI_EOB2) ? 0xFF : 0) { + _debugEnabled = false; + _maskMinY = _maskMaxY = -1; + + _drawShapeVar1 = 0; + _drawShapeVar3 = 1; + _drawShapeVar4 = 0; + _drawShapeVar5 = 0; + + memset(_fonts, 0, sizeof(_fonts)); + + memset(_pagePtrs, 0, sizeof(_pagePtrs)); + // In VGA mode the odd and even page pointers point to the same buffers. + for (int i = 0; i < SCREEN_PAGE_NUM; i++) + _pageMapping[i] = i & ~1; + + _renderMode = Common::kRenderDefault; + _sjisMixedFontMode = false; + + _useHiColorScreen = _vm->gameFlags().useHiColorMode; + _screenPageSize = SCREEN_PAGE_SIZE; + _16bitPalette = 0; + _16bitConversionPalette = 0; + _16bitShadingLevel = 0; + _bytesPerPixel = 1; + + _currentFont = FID_8_FNT; + _paletteChanged = true; + _curDim = 0; +} + +Screen::~Screen() { + for (int i = 0; i < SCREEN_OVLS_NUM; ++i) + delete[] _sjisOverlayPtrs[i]; + + delete[] _pagePtrs[0]; + + for (int f = 0; f < ARRAYSIZE(_fonts); ++f) + delete _fonts[f]; + + delete _screenPalette; + delete _internFadePalette; + delete[] _decodeShapeBuffer; + delete[] _animBlockPtr; + delete[] _16bitPalette; + delete[] _16bitConversionPalette; + + for (uint i = 0; i < _palettes.size(); ++i) + delete _palettes[i]; + + for (int i = 0; i < _dimTableCount; ++i) + delete _customDimTable[i]; + delete[] _customDimTable; +} + +bool Screen::init() { + _debugEnabled = false; + + memset(_sjisOverlayPtrs, 0, sizeof(_sjisOverlayPtrs)); + _useOverlays = false; + _useSJIS = false; + _use16ColorMode = _vm->gameFlags().use16ColorMode; + _isAmiga = (_vm->gameFlags().platform == Common::kPlatformAmiga); + + // We only check the "render_mode" setting for both Eye of the Beholder + // games here, since all the other games do not support the render_mode + // setting or handle it differently, like Kyra 1 PC-98. This avoids + // graphics glitches and crashes in other games, when the user sets his + // global render_mode setting to EGA for example. + // TODO/FIXME: It would be nice not to hardcode this. But there is no + // trivial/non annoying way to do mode checks in an easy fashion right + // now. + // In a more general sense, we might want to think about a way to only + // pass valid config values, as in values which the engine can work with, + // to the engines. We already limit the selection via our GUIO flags in + // the game specific settings, but this is not enough due to global + // settings allowing everything. + if (_vm->game() == GI_EOB1 || _vm->game() == GI_EOB2) { + if (ConfMan.hasKey("render_mode")) + _renderMode = Common::parseRenderMode(ConfMan.get("render_mode")); + } + + // CGA and EGA modes use additional pages to do the CGA/EGA specific graphics conversions. + if (_vm->game() == GI_EOB1 && (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderEGA)) { + for (int i = 0; i < 8; i++) + _pageMapping[i] = i; + } + + memset(_fonts, 0, sizeof(_fonts)); + + _useOverlays = (_vm->gameFlags().useHiRes && _renderMode != Common::kRenderEGA); + + if (_useOverlays) { + _useSJIS = (_vm->gameFlags().lang == Common::JA_JPN); + _sjisInvisibleColor = (_vm->game() == GI_KYRA1) ? 0x80 : 0xF6; + _sjisMixedFontMode = !_use16ColorMode; + + if (!_sjisOverlayPtrs[0]) { + // We alway assume 2 bytes per pixel here when the backend is in hicolor mode, since this is the surface that is passed to the backend. + // We do this regardsless of the paramater sent to enableHiColorMode() so as not to have to change the backend color mode. + // Conversions from 8bit to 16bit have to take place when copying data to this surface here. + int bpp = _useHiColorScreen ? 2 : 1; + _sjisOverlayPtrs[0] = new uint8[SCREEN_OVL_SJIS_SIZE * bpp]; + assert(_sjisOverlayPtrs[0]); + memset(_sjisOverlayPtrs[0], _sjisInvisibleColor, SCREEN_OVL_SJIS_SIZE * bpp); + } + + for (int i = 1; i < SCREEN_OVLS_NUM; ++i) { + if (!_sjisOverlayPtrs[i]) { + _sjisOverlayPtrs[i] = new uint8[SCREEN_OVL_SJIS_SIZE]; + assert(_sjisOverlayPtrs[i]); + memset(_sjisOverlayPtrs[i], _sjisInvisibleColor, SCREEN_OVL_SJIS_SIZE); + } + } + + if (_useSJIS) { + Graphics::FontSJIS *font = Graphics::FontSJIS::createFont(_vm->gameFlags().platform); + + if (!font) + error("Could not load any SJIS font, neither the original nor ScummVM's 'SJIS.FNT'"); + + _fonts[FID_SJIS_FNT] = new SJISFont(font, _sjisInvisibleColor, _use16ColorMode, !_use16ColorMode && _vm->game() != GI_LOL && _vm->game() != GI_EOB2, _vm->game() == GI_EOB2 && _vm->gameFlags().platform == Common::kPlatformFMTowns, !_use16ColorMode && _vm->game() == GI_LOL ? 1 : 0); + } + } + + _curPage = 0; + + enableHiColorMode(false); + + memset(_shapePages, 0, sizeof(_shapePages)); + + const int paletteCount = _isAmiga ? 13 : 4; + // We allow 256 color palettes in EGA mode, since original EOB II code does the same and requires it + const int numColors = _use16ColorMode ? 16 : (_isAmiga ? 32 : (_renderMode == Common::kRenderCGA ? 4 : 256)); + + _interfacePaletteEnabled = false; + + _screenPalette = new Palette(numColors); + assert(_screenPalette); + + _palettes.resize(paletteCount); + for (int i = 0; i < paletteCount; ++i) { + _palettes[i] = new Palette(numColors); + assert(_palettes[i]); + } + + // Setup CGA colors (if CGA mode is selected) + if (_renderMode == Common::kRenderCGA) { + Palette pal(5); + pal.setCGAPalette(1, Palette::kIntensityHigh); + // create additional black color 4 for use with the mouse cursor manager + pal.fill(4, 1, 0); + Screen::setScreenPalette(pal); + } + + _internFadePalette = new Palette(numColors); + assert(_internFadePalette); + + setScreenPalette(getPalette(0)); + + // We setup the PC98 text mode palette at [16, 24], since that will be used + // for KANJI characters in Lands of Lore. + if (_use16ColorMode && _vm->gameFlags().platform == Common::kPlatformPC98) { + uint8 palette[8 * 3]; + + for (int i = 0; i < 8; ++i) { + palette[i * 3 + 0] = ((i >> 1) & 1) * 0xFF; + palette[i * 3 + 1] = ((i >> 2) & 1) * 0xFF; + palette[i * 3 + 2] = ((i >> 0) & 1) * 0xFF; + } + + _system->getPaletteManager()->setPalette(palette, 16, 8); + } + + _customDimTable = new ScreenDim *[_dimTableCount]; + memset(_customDimTable, 0, sizeof(ScreenDim *) * _dimTableCount); + + _curDimIndex = -1; + _curDim = 0; + _charWidth = 0; + _charOffset = 0; + for (int i = 0; i < ARRAYSIZE(_textColorsMap); ++i) + _textColorsMap[i] = i; + _textColorsMap16bit[0] = _textColorsMap16bit[1] = 0; + _decodeShapeBuffer = NULL; + _decodeShapeBufferSize = 0; + _animBlockPtr = NULL; + _animBlockSize = 0; + _mouseLockCount = 1; + CursorMan.showMouse(false); + + _forceFullUpdate = false; + + return true; +} + +bool Screen::enableScreenDebug(bool enable) { + bool temp = _debugEnabled; + + if (_debugEnabled != enable) { + _debugEnabled = enable; + setResolution(); + _forceFullUpdate = true; + updateScreen(); + } + + return temp; +} + +void Screen::setResolution() { + byte palette[3 * 256]; + if (!_useHiColorScreen) + _system->getPaletteManager()->grabPalette(palette, 0, 256); + + int width = 320, height = 200; + + if (_vm->gameFlags().useHiRes) { + height = 400; + + if (_debugEnabled) + width = 960; + else + width = 640; + } else { + if (_debugEnabled) + width = 640; + else + width = 320; + } + + if (_useHiColorScreen) { + Graphics::PixelFormat px(2, 5, 5, 5, 0, 10, 5, 0, 0); + Common::List tryModes = _system->getSupportedFormats(); + for (Common::List::iterator g = tryModes.begin(); g != tryModes.end(); ++g) { + if (g->bytesPerPixel != 2 || g->aBits()) { + g = tryModes.reverse_erase(g); + } else if (*g == px) { + tryModes.clear(); + tryModes.push_back(px); + break; + } + } + initGraphics(width, height, tryModes); + if (_system->getScreenFormat().bytesPerPixel != 2) + error("Required graphics mode not supported by platform."); + + } else { + initGraphics(width, height); + _system->getPaletteManager()->setPalette(palette, 0, 256); + } +} + +void Screen::enableHiColorMode(bool enabled) { + if (_useHiColorScreen && enabled) { + if (!_16bitPalette) + _16bitPalette = new uint16[1024]; + memset(_16bitPalette, 0, 1024 * sizeof(uint16)); + delete[] _16bitConversionPalette; + _16bitConversionPalette = 0; + _bytesPerPixel = 2; + } else { + if (_useHiColorScreen) { + if (!_16bitConversionPalette) + _16bitConversionPalette = new uint16[256]; + memset(_16bitConversionPalette, 0, 256 * sizeof(uint16)); + } + + delete[] _16bitPalette; + _16bitPalette = 0; + _bytesPerPixel = 1; + } + + resetPagePtrsAndBuffers(SCREEN_PAGE_SIZE * _bytesPerPixel); +} + +void Screen::updateScreen() { + bool needRealUpdate = _forceFullUpdate || !_dirtyRects.empty() || _paletteChanged; + _paletteChanged = false; + + if (_useOverlays) + updateDirtyRectsOvl(); + else if (_isAmiga && _interfacePaletteEnabled) + updateDirtyRectsAmiga(); + else + updateDirtyRects(); + + if (_debugEnabled) { + needRealUpdate = true; + + if (!_useOverlays) + _system->copyRectToScreen(getPagePtr(2), SCREEN_W, 320, 0, SCREEN_W, SCREEN_H); + else + _system->copyRectToScreen(getPagePtr(2), SCREEN_W, 640, 0, SCREEN_W, SCREEN_H); + } + + if (needRealUpdate) + _system->updateScreen(); +} + +void Screen::updateDirtyRects() { + if (_forceFullUpdate) { + _system->copyRectToScreen(getCPagePtr(0), SCREEN_W, 0, 0, SCREEN_W, SCREEN_H); + } else { + const byte *page0 = getCPagePtr(0); + Common::List::iterator it; + for (it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) { + _system->copyRectToScreen(page0 + it->top * SCREEN_W + it->left, SCREEN_W, it->left, it->top, it->width(), it->height()); + } + } + _forceFullUpdate = false; + _dirtyRects.clear(); +} + +void Screen::updateDirtyRectsAmiga() { + if (_forceFullUpdate) { + _system->copyRectToScreen(getCPagePtr(0), SCREEN_W, 0, 0, SCREEN_W, 136); + + // Page 8 is not used by Kyra 1 AMIGA, thus we can use it to adjust the colors + copyRegion(0, 136, 0, 0, 320, 64, 0, 8, CR_NO_P_CHECK); + + uint8 *dst = getPagePtr(8); + for (int y = 0; y < 64; ++y) + for (int x = 0; x < 320; ++x) + *dst++ += 32; + + _system->copyRectToScreen(getCPagePtr(8), SCREEN_W, 0, 136, SCREEN_W, 64); + } else { + const byte *page0 = getCPagePtr(0); + Common::List::iterator it; + + for (it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) { + if (it->bottom <= 136) { + _system->copyRectToScreen(page0 + it->top * SCREEN_W + it->left, SCREEN_W, it->left, it->top, it->width(), it->height()); + } else { + // Check whether the rectangle is part of both the screen and the interface + if (it->top < 136) { + // The rectangle covers both screen part and interface part + + const int screenHeight = 136 - it->top; + const int interfaceHeight = it->bottom - 136; + + const int width = it->width(); + const int lineAdd = SCREEN_W - width; + + // Copy the screen part verbatim + _system->copyRectToScreen(page0 + it->top * SCREEN_W + it->left, SCREEN_W, it->left, it->top, width, screenHeight); + + // Adjust the interface part + copyRegion(it->left, 136, 0, 0, width, interfaceHeight, 0, 8, Screen::CR_NO_P_CHECK); + + uint8 *dst = getPagePtr(8); + for (int y = 0; y < interfaceHeight; ++y) { + for (int x = 0; x < width; ++x) + *dst++ += 32; + dst += lineAdd; + } + + _system->copyRectToScreen(getCPagePtr(8), SCREEN_W, it->left, 136, width, interfaceHeight); + } else { + // The rectangle only covers the interface part + + const int width = it->width(); + const int height = it->height(); + const int lineAdd = SCREEN_W - width; + + copyRegion(it->left, it->top, 0, 0, width, height, 0, 8, Screen::CR_NO_P_CHECK); + + uint8 *dst = getPagePtr(8); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) + *dst++ += 32; + dst += lineAdd; + } + + _system->copyRectToScreen(getCPagePtr(8), SCREEN_W, it->left, it->top, width, height); + } + } + } + } + + _forceFullUpdate = false; + _dirtyRects.clear(); +} + +void Screen::updateDirtyRectsOvl() { + if (_forceFullUpdate) { + const byte *src = getCPagePtr(0); + byte *dst = _sjisOverlayPtrs[0]; + scale2x(dst, 640, src, SCREEN_W, SCREEN_W, SCREEN_H); + mergeOverlay(0, 0, 640, 400); + _system->copyRectToScreen(dst, _useHiColorScreen ? 1280 : 640, 0, 0, 640, 400); + } else { + const byte *page0 = getCPagePtr(0); + byte *ovl0 = _sjisOverlayPtrs[0]; + int dstBpp = _useHiColorScreen ? 2 : 1; + + Common::List::iterator it; + for (it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) { + byte *dst = ovl0 + it->top * 1280 * dstBpp + (it->left << dstBpp); + const byte *src = page0 + it->top * SCREEN_W * _bytesPerPixel + it->left * _bytesPerPixel; + + scale2x(dst, 640, src, SCREEN_W, it->width(), it->height()); + mergeOverlay(it->left<<1, it->top<<1, it->width()<<1, it->height()<<1); + _system->copyRectToScreen(dst, _useHiColorScreen ? 1280 : 640, it->left << 1, it->top << 1, it->width() << 1, it->height() << 1); + } + } + + _forceFullUpdate = false; + _dirtyRects.clear(); +} + +void Screen::scale2x(byte *dst, int dstPitch, const byte *src, int srcPitch, int w, int h) { + int srcBpp = _bytesPerPixel; + int dstBpp = _useHiColorScreen ? 2 : 1; + + byte *dstL1 = dst; + byte *dstL2 = dst + dstPitch * dstBpp; + + int dstAdd = (dstPitch * 2 - w * 2) * dstBpp; + int srcAdd = (srcPitch - w) * srcBpp; + int dstInc = 2 * dstBpp; + + while (h--) { + for (int x = 0; x < w; x++, src += srcBpp, dstL1 += dstInc, dstL2 += dstInc) { + if (dstBpp == 1) { + uint16 col = *src; + col |= col << 8; + *(uint16 *)(dstL1) = *(uint16 *)(dstL2) = col; + } else if (dstBpp == srcBpp) { + uint32 col = *(const uint16 *)src; + col |= col << 16; + *(uint32 *)(dstL1) = *(uint32 *)(dstL2) = col; + } else if (dstBpp == 2) { + uint32 col = _16bitConversionPalette[*src]; + col |= col << 16; + *(uint32 *)(dstL1) = *(uint32 *)(dstL2) = col; + } + } + dstL1 += dstAdd; dstL2 += dstAdd; + src += srcAdd; + } +} + +void Screen::mergeOverlay(int x, int y, int w, int h) { + int bpp = _useHiColorScreen ? 2 : 1; + byte *dst = _sjisOverlayPtrs[0] + y * 640 * bpp + x * bpp; + const byte *src = _sjisOverlayPtrs[1] + y * 640 + x; + uint16 *p16 = _16bitPalette ? _16bitPalette : (_16bitConversionPalette ? _16bitConversionPalette : 0); + + int add = 640 - w; + + while (h--) { + for (x = 0; x < w; ++x, dst += bpp) { + byte col = *src++; + if (col != _sjisInvisibleColor) { + if (bpp == 2) + *(uint16*)dst = p16[col]; + else + *dst = col; + } + } + dst += add * bpp; + src += add; + } +} + +const ScreenDim *Screen::getScreenDim(int dim) const { + assert(dim < _dimTableCount); + return _customDimTable[dim] ? _customDimTable[dim] : &_dimTable[dim]; +} + +void Screen::modifyScreenDim(int dim, int x, int y, int w, int h) { + if (!_customDimTable[dim]) + _customDimTable[dim] = new ScreenDim; + + memcpy(_customDimTable[dim], &_dimTable[dim], sizeof(ScreenDim)); + _customDimTable[dim]->sx = x; + _customDimTable[dim]->sy = y; + _customDimTable[dim]->w = w; + _customDimTable[dim]->h = h; + if (dim == _curDimIndex || _vm->game() == GI_LOL) + setScreenDim(dim); +} + +void Screen::setScreenDim(int dim) { + _curDim = getScreenDim(dim); + _curDimIndex = dim; +} + +void Screen::resetPagePtrsAndBuffers(int pageSize) { + _screenPageSize = pageSize; + + delete[] _pagePtrs[0]; + memset(_pagePtrs, 0, sizeof(_pagePtrs)); + + Common::Array realPages; + for (int i = 0; i < SCREEN_PAGE_NUM; i++) { + if (Common::find(realPages.begin(), realPages.end(), _pageMapping[i]) == realPages.end()) + realPages.push_back(_pageMapping[i]); + } + + int numPages = realPages.size(); + uint32 bufferSize = numPages * _screenPageSize; + + uint8 *pagePtr = new uint8[bufferSize]; + memset(pagePtr, 0, bufferSize); + + memset(_pagePtrs, 0, sizeof(_pagePtrs)); + for (int i = 0; i < SCREEN_PAGE_NUM; i++) { + if (_pagePtrs[_pageMapping[i]]) { + _pagePtrs[i] = _pagePtrs[_pageMapping[i]]; + } else { + _pagePtrs[i] = pagePtr; + pagePtr += _screenPageSize; + } + } +} + +uint8 *Screen::getPagePtr(int pageNum) { + assert(pageNum < SCREEN_PAGE_NUM); + return _pagePtrs[pageNum]; +} + +const uint8 *Screen::getCPagePtr(int pageNum) const { + assert(pageNum < SCREEN_PAGE_NUM); + return _pagePtrs[pageNum]; +} + +uint8 *Screen::getPageRect(int pageNum, int x, int y, int w, int h) { + assert(pageNum < SCREEN_PAGE_NUM); + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, w, h); + return _pagePtrs[pageNum] + y * SCREEN_W + x; +} + +void Screen::clearPage(int pageNum) { + assert(pageNum < SCREEN_PAGE_NUM); + if (pageNum == 0 || pageNum == 1) + _forceFullUpdate = true; + memset(getPagePtr(pageNum), 0, _screenPageSize); + clearOverlayPage(pageNum); +} + +int Screen::setCurPage(int pageNum) { + assert(pageNum < SCREEN_PAGE_NUM); + int previousPage = _curPage; + _curPage = pageNum; + return previousPage; +} + +void Screen::clearCurPage() { + if (_curPage == 0 || _curPage == 1) + _forceFullUpdate = true; + memset(getPagePtr(_curPage), 0, _screenPageSize); + clearOverlayPage(_curPage); +} + +void Screen::copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src, + int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2) { + uint8 *dstPtr = getPagePtr(_curPage); + uint8 *origDst = dstPtr; + + const ScreenDim *dim = getScreenDim(dimState); + int dimX1 = dim->sx << 3; + int dimX2 = dim->w << 3; + dimX2 += dimX1; + + int dimY1 = dim->sy; + int dimY2 = dim->h; + dimY2 += dimY1; + + int temp = y - dimY1; + if (temp < 0) { + if ((temp += h) <= 0) + return; + else { + SWAP(temp, h); + y += temp - h; + src += (temp - h) * w; + } + } + + temp = dimY2 - y; + if (temp <= 0) + return; + + if (temp < h) + h = temp; + + int srcOffset = 0; + temp = x - dimX1; + if (temp < 0) { + temp = -temp; + srcOffset = temp; + x += temp; + w -= temp; + } + + int srcAdd = 0; + + temp = dimX2 - x; + if (temp <= 0) + return; + + if (temp < w) { + SWAP(w, temp); + temp -= w; + srcAdd = temp; + } + + dstPtr += y * SCREEN_W + x; + uint8 *dst = dstPtr; + + if (_curPage == 0 || _curPage == 1) + addDirtyRect(x, y, w, h); + + if (!_use16ColorMode) + clearOverlayRect(_curPage, x, y, w, h); + + temp = h; + int curY = y; + while (h--) { + src += srcOffset; + ++curY; + int cW = w; + + switch (plotFunc) { + case 0: + memcpy(dst, src, cW); + dst += cW; src += cW; + break; + + case 1: + while (cW--) { + uint8 d = *src++; + uint8 t = unkPtr1[d]; + if (t != 0xFF) + d = unkPtr2[*dst + (t << 8)]; + *dst++ = d; + } + break; + + case 4: + while (cW--) { + uint8 d = *src++; + if (d) + *dst = d; + ++dst; + } + break; + + case 5: + while (cW--) { + uint8 d = *src++; + if (d) { + uint8 t = unkPtr1[d]; + if (t != 0xFF) + d = unkPtr2[*dst + (t << 8)]; + *dst = d; + } + ++dst; + } + break; + + case 8: + case 9: + while (cW--) { + uint8 d = *src++; + uint8 t = _shapePages[0][dst - origDst] & 7; + if (unk1 < t && (curY > _maskMinY && curY < _maskMaxY)) + d = _shapePages[1][dst - origDst]; + *dst++ = d; + } + break; + + case 12: + case 13: + while (cW--) { + uint8 d = *src++; + if (d) { + uint8 t = _shapePages[0][dst - origDst] & 7; + if (unk1 < t && (curY > _maskMinY && curY < _maskMaxY)) + d = _shapePages[1][dst - origDst]; + *dst++ = d; + } else { + d = _shapePages[1][dst - origDst]; + *dst++ = d; + } + } + break; + + default: + break; + } + + dst = (dstPtr += SCREEN_W); + src += srcAdd; + } +} + +int Screen::getPagePixel(int pageNum, int x, int y) { + assert(pageNum < SCREEN_PAGE_NUM); + assert(x >= 0 && x < SCREEN_W && y >= 0 && y < SCREEN_H); + if (_bytesPerPixel == 1) + return _pagePtrs[pageNum][y * SCREEN_W + x]; + else + return ((uint16*)_pagePtrs[pageNum])[y * SCREEN_W + x]; +} + +void Screen::setPagePixel(int pageNum, int x, int y, uint8 color) { + assert(pageNum < SCREEN_PAGE_NUM); + assert(x >= 0 && x < SCREEN_W && y >= 0 && y < SCREEN_H); + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, 1, 1); + + if (_use16ColorMode) { + color &= 0x0F; + color |= (color << 4); + } else if (_renderMode == Common::kRenderCGA) { + color &= 0x03; + } else if (_renderMode == Common::kRenderEGA && !_useHiResEGADithering) { + color &= 0x0F; + } + + if (_bytesPerPixel == 2) { + ((uint16*)_pagePtrs[pageNum])[y * SCREEN_W + x] = _16bitPalette[color]; + } else { + _pagePtrs[pageNum][y * SCREEN_W + x] = color; + } +} + +void Screen::fadeFromBlack(int delay, const UpdateFunctor *upFunc) { + fadePalette(getPalette(0), delay, upFunc); +} + +void Screen::fadeToBlack(int delay, const UpdateFunctor *upFunc) { + if (_renderMode == Common::kRenderEGA) + return; + + Palette pal(getPalette(0).getNumColors()); + fadePalette(pal, delay, upFunc); +} + +void Screen::fadePalette(const Palette &pal, int delay, const UpdateFunctor *upFunc) { + if (_renderMode == Common::kRenderEGA || _bytesPerPixel == 2) + setScreenPalette(pal); + + updateScreen(); + + if (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderEGA || _bytesPerPixel == 2) + return; + + int diff = 0, delayInc = 0; + getFadeParams(pal, delay, delayInc, diff); + + int delayAcc = 0; + while (!_vm->shouldQuit()) { + delayAcc += delayInc; + + int refreshed = fadePalStep(pal, diff); + + if (upFunc && upFunc->isValid()) + (*upFunc)(); + else if (_useHiColorScreen) + updateScreen(); + else + _system->updateScreen(); + + if (!refreshed) + break; + + _vm->delay((delayAcc >> 8) * 1000 / 60); + delayAcc &= 0xFF; + } + + // In case we should quit we setup the final palette here. This avoids + // ugly palette glitches when quitting while fading. This can for example + // be noticed when quitting while viewing the family album in Kyra3. + if (_vm->shouldQuit()) { + setScreenPalette(pal); + } +} + +void Screen::getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff) { + uint8 maxDiff = 0; + + for (int i = 0; i < pal.getNumColors() * 3; ++i) { + diff = ABS(pal[i] - (*_screenPalette)[i]); + maxDiff = MAX(maxDiff, diff); + } + + delayInc = (delay << 8) & 0x7FFF; + if (maxDiff != 0) + delayInc /= maxDiff; + + delay = delayInc; + for (diff = 1; diff <= maxDiff; ++diff) { + if (delayInc >= 512) + break; + delayInc += delay; + } +} + +int Screen::fadePalStep(const Palette &pal, int diff) { + _internFadePalette->copy(*_screenPalette); + + bool needRefresh = false; + + for (int i = 0; i < pal.getNumColors() * 3; ++i) { + int c1 = pal[i]; + int c2 = (*_internFadePalette)[i]; + if (c1 != c2) { + needRefresh = true; + if (c1 > c2) { + c2 += diff; + if (c1 < c2) + c2 = c1; + } + + if (c1 < c2) { + c2 -= diff; + if (c1 > c2) + c2 = c1; + } + + (*_internFadePalette)[i] = (uint8)c2; + } + } + + if (needRefresh) + setScreenPalette(*_internFadePalette); + + return needRefresh ? 1 : 0; +} + +void Screen::setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue) { + Palette &pal = getPalette(0); + + const int offset = index * 3; + + if (pal[offset + 0] == red && pal[offset + 1] == green && pal[offset + 2] == blue) + return; + + pal[offset + 0] = red; + pal[offset + 1] = green; + pal[offset + 2] = blue; + + setScreenPalette(pal); +} + +void Screen::getRealPalette(int num, uint8 *dst) { + const int colors = _use16ColorMode ? 16 : (_isAmiga ? 32 : 256); + const uint8 *palData = getPalette(num).getData(); + + if (!palData) { + memset(dst, 0, colors * 3); + return; + } + + for (int i = 0; i < colors; ++i) { + dst[0] = (palData[0] * 0xFF) / 0x3F; + dst[1] = (palData[1] * 0xFF) / 0x3F; + dst[2] = (palData[2] * 0xFF) / 0x3F; + dst += 3; + palData += 3; + } +} + +void Screen::setScreenPalette(const Palette &pal) { + uint8 screenPal[256 * 3]; + _screenPalette->copy(pal); + + for (int i = 0; i < pal.getNumColors(); ++i) { + screenPal[3 * i + 0] = (pal[i * 3 + 0] * 0xFF) / 0x3F; + screenPal[3 * i + 1] = (pal[i * 3 + 1] * 0xFF) / 0x3F; + screenPal[3 * i + 2] = (pal[i * 3 + 2] * 0xFF) / 0x3F; + } + + _paletteChanged = true; + + if (_useHiColorScreen) { + if (_16bitPalette) + memcpy(_16bitPalette, pal.getData(), 512); + + // Generate 16bit palette for the 8bit/16 bit conversion in scale2x() + if (_16bitConversionPalette) { + Graphics::PixelFormat pixelFormat = _system->getScreenFormat(); + for (int i = 0; i < 256; ++i) + _16bitConversionPalette[i] = pixelFormat.RGBToColor(screenPal[i * 3], screenPal[i * 3 + 1], screenPal[i * 3 + 2]); + // The whole Surface has to be converted again after each palette chance + _forceFullUpdate = true; + } + return; + } + + _system->getPaletteManager()->setPalette(screenPal, 0, pal.getNumColors()); +} + +void Screen::enableInterfacePalette(bool e) { + _interfacePaletteEnabled = e; + + _forceFullUpdate = true; + _dirtyRects.clear(); + + // TODO: We might need to reset the mouse cursor + + updateScreen(); +} + +void Screen::setInterfacePalette(const Palette &pal, uint8 r, uint8 g, uint8 b) { + if (!_isAmiga) + return; + + uint8 screenPal[32 * 3]; + + assert(32 <= pal.getNumColors()); + + for (int i = 0; i < pal.getNumColors(); ++i) { + if (i != 0x10) { + screenPal[3 * i + 0] = (pal[i * 3 + 0] * 0xFF) / 0x3F; + screenPal[3 * i + 1] = (pal[i * 3 + 1] * 0xFF) / 0x3F; + screenPal[3 * i + 2] = (pal[i * 3 + 2] * 0xFF) / 0x3F; + } else { + screenPal[3 * i + 0] = (r * 0xFF) / 0x3F; + screenPal[3 * i + 1] = (g * 0xFF) / 0x3F; + screenPal[3 * i + 2] = (b * 0xFF) / 0x3F; + } + } + + _paletteChanged = true; + _system->getPaletteManager()->setPalette(screenPal, 32, pal.getNumColors()); +} + +void Screen::copyToPage0(int y, int h, uint8 page, uint8 *seqBuf) { + assert(y + h <= SCREEN_H); + const uint8 *src = getPagePtr(page) + y * SCREEN_W; + uint8 *dstPage = getPagePtr(0) + y * SCREEN_W; + for (int i = 0; i < h; ++i) { + for (int x = 0; x < SCREEN_W; ++x) { + if (seqBuf[x] != src[x]) { + seqBuf[x] = src[x]; + dstPage[x] = src[x]; + } + } + src += SCREEN_W; + seqBuf += SCREEN_W; + dstPage += SCREEN_W; + } + addDirtyRect(0, y, SCREEN_W, h); + // This would remove the text in the end sequence of + // the (Kyrandia 1) FM-TOWNS version. + // Since this method is just used for the Seqplayer + // this shouldn't be a problem anywhere else, so it's + // safe to disable the call here. + //clearOverlayRect(0, 0, y, SCREEN_W, h); +} + +void Screen::copyRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage, int flags) { + if (x2 < 0) { + if (x2 <= -w) + return; + w += x2; + x1 -= x2; + x2 = 0; + } else if (x2 + w >= SCREEN_W) { + if (x2 > SCREEN_W) + return; + w = SCREEN_W - x2; + } + + if (y2 < 0) { + if (y2 <= -h) + return; + h += y2; + y1 -= y2; + y2 = 0; + } else if (y2 + h >= SCREEN_H) { + if (y2 > SCREEN_H) + return; + h = SCREEN_H - y2; + } + + const uint8 *src = getPagePtr(srcPage) + y1 * SCREEN_W * _bytesPerPixel + x1 * _bytesPerPixel; + uint8 *dst = getPagePtr(dstPage) + y2 * SCREEN_W * _bytesPerPixel + x2 * _bytesPerPixel; + + if (src == dst) + return; + + if (dstPage == 0 || dstPage == 1) + addDirtyRect(x2, y2, w, h); + + copyOverlayRegion(x1, y1, x2, y2, w, h, srcPage, dstPage); + + if (flags & CR_NO_P_CHECK) { + while (h--) { + memmove(dst, src, w * _bytesPerPixel); + src += SCREEN_W * _bytesPerPixel; + dst += SCREEN_W * _bytesPerPixel; + } + } else { + while (h--) { + for (int i = 0; i < w; ++i) { + if (_bytesPerPixel == 2) { + uint px = *(const uint16*)&src[i << 1]; + if (px) + *(uint16*)&dst[i << 1] = px; + } else { + if (src[i]) + dst[i] = src[i]; + } + } + src += SCREEN_W * _bytesPerPixel; + dst += SCREEN_W * _bytesPerPixel; + } + } +} + +void Screen::copyRegionToBuffer(int pageNum, int x, int y, int w, int h, uint8 *dest) { + if (y < 0) { + dest += (-y) * w * _bytesPerPixel; + h += y; + y = 0; + } else if (y + h > SCREEN_H) { + h = SCREEN_H - y; + } + + if (x < 0) { + dest += -x * _bytesPerPixel; + w += x; + x = 0; + } else if (x + w > SCREEN_W) { + w = SCREEN_W - x; + } + + if (w < 0 || h < 0) + return; + + uint8 *pagePtr = getPagePtr(pageNum); + + for (int i = y; i < y + h; ++i) + memcpy(dest + (i - y) * w * _bytesPerPixel, pagePtr + i * SCREEN_W * _bytesPerPixel + x * _bytesPerPixel, w * _bytesPerPixel); +} + +void Screen::copyPage(uint8 srcPage, uint8 dstPage) { + uint8 *src = getPagePtr(srcPage); + uint8 *dst = getPagePtr(dstPage); + if (src != dst) + memcpy(dst, src, SCREEN_W * SCREEN_H * _bytesPerPixel); + copyOverlayRegion(0, 0, 0, 0, SCREEN_W, SCREEN_H, srcPage, dstPage); + + if (dstPage == 0 || dstPage == 1) + _forceFullUpdate = true; +} + +void Screen::copyBlockToPage(int pageNum, int x, int y, int w, int h, const uint8 *src) { + if (y < 0) { + src += (-y) * w * _bytesPerPixel; + h += y; + y = 0; + } else if (y + h > SCREEN_H) { + h = SCREEN_H - y; + } + + if (x < 0) { + src += -x * _bytesPerPixel; + w += x; + x = 0; + } else if (x + w > SCREEN_W) { + w = SCREEN_W - x; + } + + if (w < 0 || h < 0) + return; + + uint8 *dst = getPagePtr(pageNum) + y * SCREEN_W * _bytesPerPixel + x * _bytesPerPixel; + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, w, h); + + clearOverlayRect(pageNum, x, y, w, h); + + while (h--) { + memcpy(dst, src, w * _bytesPerPixel); + dst += SCREEN_W * _bytesPerPixel; + src += w * _bytesPerPixel; + } +} + +void Screen::shuffleScreen(int sx, int sy, int w, int h, int srcPage, int dstPage, int ticks, bool transparent) { + assert(sx >= 0 && w <= SCREEN_W); + int x; + uint16 x_offs[SCREEN_W]; + for (x = 0; x < SCREEN_W; ++x) + x_offs[x] = x; + + for (x = 0; x < w; ++x) { + int i = _vm->_rnd.getRandomNumber(w - 1); + SWAP(x_offs[x], x_offs[i]); + } + + assert(sy >= 0 && h <= SCREEN_H); + int y; + uint8 y_offs[SCREEN_H]; + for (y = 0; y < SCREEN_H; ++y) + y_offs[y] = y; + + for (y = 0; y < h; ++y) { + int i = _vm->_rnd.getRandomNumber(h - 1); + SWAP(y_offs[y], y_offs[i]); + } + + int32 start, now; + int wait; + for (y = 0; y < h && !_vm->shouldQuit(); ++y) { + start = (int32)_system->getMillis(); + int y_cur = y; + for (x = 0; x < w; ++x) { + int i = sx + x_offs[x]; + int j = sy + y_offs[y_cur]; + ++y_cur; + if (y_cur >= h) + y_cur = 0; + + uint8 color = getPagePixel(srcPage, i, j); + if (!transparent || color != 0) + setPagePixel(dstPage, i, j, color); + } + // forcing full update for now + _forceFullUpdate = true; + updateScreen(); + now = (int32)_system->getMillis(); + wait = ticks * _vm->tickLength() - (now - start); + if (wait > 0) + _vm->delay(wait); + } + + copyOverlayRegion(sx, sy, sx, sy, w, h, srcPage, dstPage); + + if (_vm->shouldQuit()) { + copyRegion(sx, sy, sx, sy, w, h, srcPage, dstPage); + _system->updateScreen(); + } +} + +void Screen::fillRect(int x1, int y1, int x2, int y2, uint8 color, int pageNum, bool xored) { + assert(x2 < SCREEN_W && y2 < SCREEN_H); + uint16 color16 = 0; + if (pageNum == -1) + pageNum = _curPage; + + uint8 *dst = getPagePtr(pageNum) + y1 * SCREEN_W * _bytesPerPixel + x1 * _bytesPerPixel; + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x1, y1, x2-x1+1, y2-y1+1); + + clearOverlayRect(pageNum, x1, y1, x2-x1+1, y2-y1+1); + + if (_use16ColorMode) { + color &= 0x0F; + color |= (color << 4); + } else if (_renderMode == Common::kRenderCGA) { + color &= 0x03; + } else if (_renderMode == Common::kRenderEGA && !_useHiResEGADithering) { + color &= 0x0F; + } else if (_bytesPerPixel == 2) + color16 = shade16bitColor(_16bitPalette[color]); + + if (xored) { + // no 16 bit support for this (unneeded) + for (; y1 <= y2; ++y1) { + for (int x = x1; x <= x2; ++x) + dst[x] ^= color; + dst += SCREEN_W; + } + } else { + for (; y1 <= y2; ++y1) { + if (_bytesPerPixel == 2) { + uint16 *ptr = (uint16*)dst; + for (int i = 0; i < x2 - x1 + 1; i++) + *ptr++ = color16; + } else { + memset(dst, color, x2 - x1 + 1); + } + dst += SCREEN_W * _bytesPerPixel; + } + } +} + +void Screen::drawBox(int x1, int y1, int x2, int y2, int color) { + drawClippedLine(x1, y1, x2, y1, color); + drawClippedLine(x1, y1, x1, y2, color); + drawClippedLine(x2, y1, x2, y2, color); + drawClippedLine(x1, y2, x2, y2, color); +} + +void Screen::drawShadedBox(int x1, int y1, int x2, int y2, int color1, int color2) { + assert(x1 >= 0 && y1 >= 0); + fillRect(x1, y1, x2, y1 + 1, color1); + fillRect(x2 - 1, y1, x2, y2, color1); + + drawClippedLine(x1, y1, x1, y2, color2); + drawClippedLine(x1 + 1, y1 + 1, x1 + 1, y2 - 1, color2); + drawClippedLine(x1, y2 - 1, x2 - 1, y2 - 1, color2); + drawClippedLine(x1, y2, x2, y2, color2); +} + +void Screen::drawClippedLine(int x1, int y1, int x2, int y2, int color) { + if (x1 < 0) + x1 = 0; + else if (x1 > 319) + x1 = 319; + + if (x2 < 0) + x2 = 0; + else if (x2 > 319) + x2 = 319; + + if (y1 < 0) + y1 = 0; + else if (y1 > 199) + y1 = 199; + + if (y2 < 0) + y2 = 0; + else if (y2 > 199) + y2 = 199; + + if (x1 == x2) + if (y1 > y2) + drawLine(true, x1, y2, y1 - y2 + 1, color); + else + drawLine(true, x1, y1, y2 - y1 + 1, color); + else + if (x1 > x2) + drawLine(false, x2, y1, x1 - x2 + 1, color); + else + drawLine(false, x1, y1, x2 - x1 + 1, color); +} + +void Screen::drawLine(bool vertical, int x, int y, int length, int color) { + uint8 *ptr = getPagePtr(_curPage) + y * SCREEN_W * _bytesPerPixel + x * _bytesPerPixel; + + if (_use16ColorMode) { + color &= 0x0F; + color |= (color << 4); + } else if (_renderMode == Common::kRenderCGA) { + color &= 0x03; + } else if (_renderMode == Common::kRenderEGA && !_useHiResEGADithering) { + color &= 0x0F; + } else if (_bytesPerPixel == 2) + color = shade16bitColor(_16bitPalette[color]); + + if (vertical) { + assert((y + length) <= SCREEN_H); + int currLine = 0; + while (currLine < length) { + if (_bytesPerPixel == 2) + *(uint16*)ptr = color; + else + *ptr = color; + ptr += SCREEN_W * _bytesPerPixel; + currLine++; + } + } else { + assert((x + length) <= SCREEN_W); + if (_bytesPerPixel == 2) { + for (int i = 0; i < length; i++) { + *(uint16*)ptr = color; + ptr += 2; + } + } else { + memset(ptr, color, length); + } + } + + if (_curPage == 0 || _curPage == 1) + addDirtyRect(x, y, (vertical) ? 1 : length, (vertical) ? length : 1); + + clearOverlayRect(_curPage, x, y, (vertical) ? 1 : length, (vertical) ? length : 1); +} + +void Screen::setAnimBlockPtr(int size) { + delete[] _animBlockPtr; + _animBlockPtr = new uint8[size]; + assert(_animBlockPtr); + memset(_animBlockPtr, 0, size); + _animBlockSize = size; +} + +void Screen::setTextColor(const uint8 *cmap8, int a, int b) { + memcpy(&_textColorsMap[a], cmap8, (b - a + 1)); + // We need to update the color tables of all fonts, we + // setup so far here. + for (int i = 0; i < FID_NUM; ++i) { + if (_fonts[i]) + _fonts[i]->setColorMap(_textColorsMap); + } +} + +void Screen::setTextColor16bit(const uint16 *cmap16) { + assert(cmap16); + _textColorsMap16bit[0] = cmap16[0]; + _textColorsMap16bit[1] = cmap16[1]; + // We need to update the color tables of all fonts, we + // setup so far here. + for (int i = 0; i < FID_NUM; ++i) { + if (_fonts[i]) + _fonts[i]->set16bitColorMap(_textColorsMap16bit); + } +} + +bool Screen::loadFont(FontId fontId, const char *filename) { + if (fontId == FID_SJIS_FNT) { + warning("Trying to replace system SJIS font"); + return true; + } + + Font *&fnt = _fonts[fontId]; + + if (!fnt) { + if (_isAmiga) + fnt = new AMIGAFont(); +#ifdef ENABLE_EOB + else if (_vm->game() == GI_EOB1 || _vm->game() == GI_EOB2) + // We use normal VGA rendering in EOB II, since we do the complete EGA dithering in updateScreen(). + fnt = new OldDOSFont(_useHiResEGADithering ? Common::kRenderVGA : _renderMode); +#endif // ENABLE_EOB + else + fnt = new DOSFont(); + + assert(fnt); + } + + Common::SeekableReadStream *file = _vm->resource()->createReadStream(filename); + if (!file) + error("Font file '%s' is missing", filename); + + bool ret = fnt->load(*file); + fnt->setColorMap(_textColorsMap); + delete file; + return ret; +} + +Screen::FontId Screen::setFont(FontId fontId) { + FontId prev = _currentFont; + _currentFont = fontId; + + assert(_fonts[_currentFont]); + return prev; +} + +int Screen::getFontHeight() const { + return _fonts[_currentFont]->getHeight(); +} + +int Screen::getFontWidth() const { + return _fonts[_currentFont]->getWidth(); +} + +int Screen::getCharWidth(uint16 c) const { + const int width = _fonts[_currentFont]->getCharWidth(c); + return width + ((_currentFont != FID_SJIS_FNT && _currentFont != FID_SJIS_LARGE_FNT && _currentFont != FID_SJIS_SMALL_FNT) ? _charWidth : 0); +} + +int Screen::getTextWidth(const char *str) { + int curLineLen = 0; + int maxLineLen = 0; + + FontId curFont = _currentFont; + + while (1) { + if (_sjisMixedFontMode && curFont != FID_SJIS_FNT && curFont != FID_SJIS_LARGE_FNT && curFont != FID_SJIS_SMALL_FNT) + setFont((*str & 0x80) ? ((_vm->game() == GI_EOB2 && curFont == FID_6_FNT) ? FID_SJIS_SMALL_FNT : FID_SJIS_FNT) : curFont); + + uint c = fetchChar(str); + + if (c == 0) { + break; + } else if (c == '\r') { + if (curLineLen > maxLineLen) + maxLineLen = curLineLen; + else + curLineLen = 0; + } else { + curLineLen += getCharWidth(c); + } + } + + return MAX(curLineLen, maxLineLen); +} + +void Screen::printText(const char *str, int x, int y, uint8 color1, uint8 color2) { + uint16 cmap16[2]; + if (_16bitPalette) { + cmap16[0] = color2 ? shade16bitColor(_16bitPalette[color2]) : 0xFFFF; + cmap16[1] = _16bitPalette[color1]; + setTextColor16bit(cmap16); + } + + uint8 cmap8[2]; + cmap8[0] = color2; + cmap8[1] = color1; + setTextColor(cmap8, 0, 1); + + FontId curFont = _currentFont; + + if (x < 0) + x = 0; + else if (x >= SCREEN_W) + return; + + int x_start = x; + if (y < 0) + y = 0; + else if (y >= SCREEN_H) + return; + + while (1) { + if (_sjisMixedFontMode && curFont != FID_SJIS_FNT && curFont != FID_SJIS_LARGE_FNT && curFont != FID_SJIS_SMALL_FNT) + setFont((*str & 0x80) ? ((_vm->game() == GI_EOB2 && curFont == FID_6_FNT) ? FID_SJIS_SMALL_FNT : FID_SJIS_FNT) : curFont); + + uint8 charHeightFnt = getFontHeight(); + + uint c = fetchChar(str); + + if (c == 0) { + break; + } else if (c == '\r') { + x = x_start; + y += (charHeightFnt + _charOffset); + } else { + int charWidth = getCharWidth(c); + if (x + charWidth > SCREEN_W) { + x = x_start; + y += (charHeightFnt + _charOffset); + if (y >= SCREEN_H) + break; + } + + drawChar(c, x, y); + x += charWidth; + } + } +} + +uint16 Screen::fetchChar(const char *&s) const { + if (_currentFont != FID_SJIS_FNT && _currentFont != FID_SJIS_LARGE_FNT && _currentFont != FID_SJIS_SMALL_FNT) + return (uint8)*s++; + + uint16 ch = (uint8)*s++; + + if (ch <= 0x7F || (ch >= 0xA1 && ch <= 0xDF)) + return ch; + + ch |= (uint8)(*s++) << 8; + return ch; +} + +void Screen::drawChar(uint16 c, int x, int y) { + Font *fnt = _fonts[_currentFont]; + assert(fnt); + + const bool useOverlay = fnt->usesOverlay(); + const int charWidth = fnt->getCharWidth(c); + const int charHeight = fnt->getHeight(); + + if (x < 0 || y < 0) + return; + if (x + charWidth > SCREEN_W || y + charHeight > SCREEN_H) + return; + + if (useOverlay) { + uint8 *destPage = getOverlayPtr(_curPage); + if (!destPage) { + warning("trying to draw SJIS char on unsupported page %d", _curPage); + return; + } + + int bpp = (_currentFont == Screen::FID_SJIS_FNT || _currentFont == Screen::FID_SJIS_SMALL_FNT) ? 1 : 2; + destPage += (y * 2) * 640 * bpp + (x * 2 * bpp); + + fnt->drawChar(c, destPage, 640, bpp); + } else { + fnt->drawChar(c, getPagePtr(_curPage) + y * SCREEN_W * _bytesPerPixel + x * _bytesPerPixel, SCREEN_W, _bytesPerPixel); + } + + if (_curPage == 0 || _curPage == 1) + addDirtyRect(x, y, charWidth, charHeight); +} + +void Screen::drawShape(uint8 pageNum, const uint8 *shapeData, int x, int y, int sd, int flags, ...) { + if (!shapeData) + return; + + if (_vm->gameFlags().useAltShapeHeader) + shapeData += 2; + + if (*shapeData & 1) + flags |= 0x400; + + va_list args; + va_start(args, flags); + + static const int drawShapeVar2[] = { + 1, 3, 2, 5, 4, 3, 2, 1 + }; + + _dsShapeFadingTable = 0; + _dsShapeFadingLevel = 0; + _dsColorTable = 0; + _dsTransparencyTable1 = 0; + _dsTransparencyTable2 = 0; + _dsBackgroundFadingTable = 0; + _dsDrawLayer = 0; + + if (flags & DSF_CUSTOM_PALETTE) { + _dsColorTable = va_arg(args, uint8 *); + } + + if (flags & DSF_SHAPE_FADING) { + _dsShapeFadingTable = va_arg(args, uint8 *); + _dsShapeFadingLevel = va_arg(args, int); + if (!_dsShapeFadingLevel) + flags &= ~DSF_SHAPE_FADING; + } + + if (flags & DSF_TRANSPARENCY) { + _dsTransparencyTable1 = va_arg(args, uint8 *); + _dsTransparencyTable2 = va_arg(args, uint8 *); + } + + if (flags & 0x200) { + _drawShapeVar1 = (_drawShapeVar1 + 1) & 0x7; + _drawShapeVar3 = drawShapeVar2[_drawShapeVar1]; + _drawShapeVar4 = 0; + _drawShapeVar5 = 256; + } + + if (flags & 0x4000) + _drawShapeVar5 = va_arg(args, int); + + if (flags & 0x800) + _dsDrawLayer = va_arg(args, int); + + if (flags & DSF_SCALE) { + _dsScaleW = va_arg(args, int); + _dsScaleH = va_arg(args, int); + } else { + _dsScaleW = 0x100; + _dsScaleH = 0x100; + } + + if ((flags & DSF_BACKGROUND_FADING) && _vm->game() != GI_KYRA1) + _dsBackgroundFadingTable = va_arg(args, uint8 *); + + va_end(args); + + static const DsMarginSkipFunc dsMarginFunc[] = { + &Screen::drawShapeMarginNoScaleUpwind, + &Screen::drawShapeMarginNoScaleDownwind, + &Screen::drawShapeMarginNoScaleUpwind, + &Screen::drawShapeMarginNoScaleDownwind, + &Screen::drawShapeMarginScaleUpwind, + &Screen::drawShapeMarginScaleDownwind, + &Screen::drawShapeMarginScaleUpwind, + &Screen::drawShapeMarginScaleDownwind + }; + + static const DsMarginSkipFunc dsSkipFunc[] = { + &Screen::drawShapeMarginNoScaleUpwind, + &Screen::drawShapeMarginNoScaleDownwind, + &Screen::drawShapeMarginNoScaleUpwind, + &Screen::drawShapeMarginNoScaleDownwind, + &Screen::drawShapeSkipScaleUpwind, + &Screen::drawShapeSkipScaleDownwind, + &Screen::drawShapeSkipScaleUpwind, + &Screen::drawShapeSkipScaleDownwind + }; + + static const DsLineFunc dsLineFunc[] = { + &Screen::drawShapeProcessLineNoScaleUpwind, + &Screen::drawShapeProcessLineNoScaleDownwind, + &Screen::drawShapeProcessLineNoScaleUpwind, + &Screen::drawShapeProcessLineNoScaleDownwind, + &Screen::drawShapeProcessLineScaleUpwind, + &Screen::drawShapeProcessLineScaleDownwind, + &Screen::drawShapeProcessLineScaleUpwind, + &Screen::drawShapeProcessLineScaleDownwind + }; + + static const DsPlotFunc dsPlotFunc[] = { + &Screen::drawShapePlotType0, // used by Kyra 1 + 2 + &Screen::drawShapePlotType1, // used by Kyra 3 + 0, + &Screen::drawShapePlotType3_7, // used by Kyra 3 (shadow) + &Screen::drawShapePlotType4, // used by Kyra 1, 2 + 3 + &Screen::drawShapePlotType5, // used by Kyra 1 + &Screen::drawShapePlotType6, // used by Kyra 1 (invisibility) + &Screen::drawShapePlotType3_7, // used by Kyra 1 (invisibility) + &Screen::drawShapePlotType8, // used by Kyra 2 + &Screen::drawShapePlotType9, // used by Kyra 1 + 3 + 0, + &Screen::drawShapePlotType11_15, // used by Kyra 1 (invisibility) + Kyra 3 (shadow) + &Screen::drawShapePlotType12, // used by Kyra 2 + &Screen::drawShapePlotType13, // used by Kyra 1 + &Screen::drawShapePlotType14, // used by Kyra 1 (invisibility) + &Screen::drawShapePlotType11_15, // used by Kyra 1 (invisibility) + &Screen::drawShapePlotType16, // used by LoL PC-98/16 Colors (teleporters), + 0, 0, 0, + &Screen::drawShapePlotType20, // used by LoL (heal spell effect) + &Screen::drawShapePlotType21, // used by LoL (white tower spirits) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + &Screen::drawShapePlotType33, // used by LoL (blood spots on the floor) + 0, 0, 0, + &Screen::drawShapePlotType37, // used by LoL (monsters) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + &Screen::drawShapePlotType48, // used by LoL (slime spots on the floor) + 0, 0, 0, + &Screen::drawShapePlotType52, // used by LoL (projectiles) + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0 + }; + + int scaleCounterV = 0; + + const int drawFunc = flags & 0x0F; + _dsProcessMargin = dsMarginFunc[drawFunc]; + _dsScaleSkip = dsSkipFunc[drawFunc]; + _dsProcessLine = dsLineFunc[drawFunc]; + + const int ppc = (flags >> 8) & 0x3F; + _dsPlot = dsPlotFunc[ppc]; + DsPlotFunc dsPlot2 = dsPlotFunc[ppc], dsPlot3 = dsPlotFunc[ppc]; + if (flags & 0x800) + dsPlot3 = dsPlotFunc[((flags >> 8) & 0xF7) & 0x3F]; + + if (!_dsPlot || !dsPlot2 || !dsPlot3) { + if (!dsPlot2) + warning("Missing drawShape plotting method type %d", ppc); + if (dsPlot3 != dsPlot2 && !dsPlot3) + warning("Missing drawShape plotting method type %d", (((flags >> 8) & 0xF7) & 0x3F)); + return; + } + + int curY = y; + const uint8 *src = shapeData; + uint8 *dst = _dsDstPage = getPagePtr(pageNum); + + const ScreenDim *dsDim = getScreenDim(sd); + dst += (dsDim->sx << 3); + + if (!(flags & 0x10)) + x -= (dsDim->sx << 3); + + int x2 = (dsDim->w << 3); + int y1 = dsDim->sy; + if (flags & 0x10) + y += y1; + + int y2 = y1 + dsDim->h; + + uint16 shapeFlags = READ_LE_UINT16(src); src += 2; + + int shapeHeight = *src++; + uint16 shapeWidth = READ_LE_UINT16(src); src += 2; + + int shpWidthScaled1 = shapeWidth; + int shpWidthScaled2 = shapeWidth; + + if (flags & DSF_SCALE) { + shapeHeight = (shapeHeight * _dsScaleH) >> 8; + shpWidthScaled1 = shpWidthScaled2 = (shapeWidth * _dsScaleW) >> 8; + + if (!shapeHeight || !shpWidthScaled1) + return; + } + + if (flags & DSF_CENTER) { + x -= (shpWidthScaled1 >> 1); + y -= (shapeHeight >> 1); + } + + src += 3; + + uint16 frameSize = READ_LE_UINT16(src); src += 2; + + int colorTableColors = ((_vm->game() != GI_KYRA1) && (shapeFlags & 4)) ? *src++ : 16; + + if (!(flags & 0x8000) && (shapeFlags & 1)) + _dsColorTable = src; + + if (flags & 0x400) + src += colorTableColors; + + if (!(shapeFlags & 2)) { + decodeFrame4(src, _animBlockPtr, frameSize); + src = _animBlockPtr; + } + + int t = (flags & 2) ? y2 - y - shapeHeight : y - y1; + + if (t < 0) { + shapeHeight += t; + if (shapeHeight <= 0) { + return; + } + + t *= -1; + const uint8 *srcBackUp = 0; + + do { + _dsOffscreenScaleVal1 = 0; + srcBackUp = src; + _dsTmpWidth = shapeWidth; + + int cnt = shapeWidth; + (this->*_dsScaleSkip)(dst, src, cnt); + + scaleCounterV += _dsScaleH; + + if (scaleCounterV & 0xFF00) { + uint8 r = scaleCounterV >> 8; + scaleCounterV &= 0xFF; + t -= r; + } + } while (!(scaleCounterV & 0xFF00) && (t > 0)); + + if (t < 0) { + src = srcBackUp; + scaleCounterV += (-t << 8); + } + + if (!(flags & 2)) + y = y1; + } + + t = (flags & 2) ? y + shapeHeight - y1 : y2 - y; + if (t <= 0) + return; + + if (t < shapeHeight) { + shapeHeight = t; + if (flags & 2) + y = y1; + } + + _dsOffscreenLeft = 0; + if (x < 0) { + shpWidthScaled1 += x; + _dsOffscreenLeft = -x; + if (_dsOffscreenLeft >= shpWidthScaled2) + return; + x = 0; + } + + _dsOffscreenRight = 0; + t = x2 - x; + + if (t <= 0) + return; + + if (t < shpWidthScaled1) { + shpWidthScaled1 = t; + _dsOffscreenRight = shpWidthScaled2 - _dsOffscreenLeft - shpWidthScaled1; + } + + int dsPitch = 320; + int ty = y; + + if (flags & 2) { + dsPitch *= -1; + ty = ty - 1 + shapeHeight; + } + + if (flags & DSF_X_FLIPPED) { + SWAP(_dsOffscreenLeft, _dsOffscreenRight); + dst += (shpWidthScaled1 - 1); + } + + dst += (320 * ty + x); + + if (flags & DSF_SCALE) { + _dsOffscreenRight = 0; + _dsOffscreenScaleVal2 = _dsOffscreenLeft; + _dsOffscreenLeft <<= 8; + _dsOffscreenScaleVal1 = (_dsOffscreenLeft % _dsScaleW) * -1; + _dsOffscreenLeft /= _dsScaleW; + } + + if (shapeHeight <= 0 || shpWidthScaled1 <= 0) + return; + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, shpWidthScaled1, shapeHeight); + clearOverlayRect(pageNum, x, y, shpWidthScaled1, shapeHeight); + + uint8 *d = dst; + + bool normalPlot = true; + while (true) { + while (!(scaleCounterV & 0xFF00)) { + scaleCounterV += _dsScaleH; + if (!(scaleCounterV & 0xFF00)) { + _dsTmpWidth = shapeWidth; + int cnt = shapeWidth; + (this->*_dsScaleSkip)(d, src, cnt); + } + } + + const uint8 *b_src = src; + + do { + src = b_src; + _dsTmpWidth = shapeWidth; + int cnt = _dsOffscreenLeft; + int scaleState = (this->*_dsProcessMargin)(d, src, cnt); + + if (_dsTmpWidth) { + cnt += shpWidthScaled1; + if (cnt > 0) { + if (flags & 0x800) + normalPlot = (curY > _maskMinY && curY < _maskMaxY); + _dsPlot = normalPlot ? dsPlot2 : dsPlot3; + (this->*_dsProcessLine)(d, src, cnt, scaleState); + } + cnt += _dsOffscreenRight; + if (cnt) + (this->*_dsScaleSkip)(d, src, cnt); + } + dst += dsPitch; + d = dst; + ++curY; + + if (!--shapeHeight) + return; + + scaleCounterV -= 0x100; + } while (scaleCounterV & 0xFF00); + } +} + +int Screen::drawShapeMarginNoScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt) { + while (cnt-- > 0) { + if (*src++) + continue; + cnt = cnt + 1 - (*src++); + } + + cnt++; + dst -= cnt; + return 0; +} + +int Screen::drawShapeMarginNoScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt) { + while (cnt-- > 0) { + if (*src++) + continue; + cnt = cnt + 1 - (*src++); + } + + cnt++; + dst += cnt; + return 0; +} + +int Screen::drawShapeMarginScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt) { + _dsTmpWidth -= cnt; + + while (cnt > 0) { + --cnt; + if (*src++) + continue; + + cnt = cnt + 1 - (*src++); + } + + if (!cnt) + return _dsOffscreenScaleVal1; + + _dsTmpWidth += cnt; + + int i = (_dsOffscreenLeft - cnt) * _dsScaleW; + int res = i & 0xFF; + i >>= 8; + i -= _dsOffscreenScaleVal2; + dst += i; + cnt = -i; + + return res; +} + +int Screen::drawShapeMarginScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt) { + _dsTmpWidth -= cnt; + + while (cnt > 0) { + --cnt; + if (*src++) + continue; + + cnt = cnt + 1 - (*src++); + } + + if (!cnt) + return _dsOffscreenScaleVal1; + + _dsTmpWidth += cnt; + + int i = (_dsOffscreenLeft - cnt) * _dsScaleW; + int res = i & 0xFF; + i >>= 8; + i -= _dsOffscreenScaleVal2; + dst -= i; + cnt = -i; + + return res; +} + +int Screen::drawShapeSkipScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt) { + cnt = _dsTmpWidth; + + if (cnt <= 0) + return 0; + + do { + --cnt; + if (*src++) + continue; + cnt = cnt + 1 - (*src++); + } while (cnt > 0); + + return 0; +} + +int Screen::drawShapeSkipScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt) { + cnt = _dsTmpWidth; + bool found = false; + + if (cnt == 0) + return 0; + + do { + --cnt; + if (*src++) + continue; + found = true; + cnt = cnt + 1 - (*src++); + } while (cnt > 0); + + return found ? 0 : _dsOffscreenScaleVal1; +} + +void Screen::drawShapeProcessLineNoScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt, int16) { + do { + uint8 c = *src++; + if (c) { + uint8 *d = dst++; + (this->*_dsPlot)(d, c); + cnt--; + } else { + c = *src++; + dst += c; + cnt -= c; + } + } while (cnt > 0); +} + +void Screen::drawShapeProcessLineNoScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt, int16) { + do { + uint8 c = *src++; + if (c) { + uint8 *d = dst--; + (this->*_dsPlot)(d, c); + cnt--; + } else { + c = *src++; + dst -= c; + cnt -= c; + } + } while (cnt > 0); +} + +void Screen::drawShapeProcessLineScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState) { + int c = 0; + + do { + if ((scaleState & 0x8000) || !(scaleState & 0xFF00)) { + c = *src++; + _dsTmpWidth--; + if (c) { + scaleState += _dsScaleW; + } else { + _dsTmpWidth++; + c = *src++; + _dsTmpWidth -= c; + int r = c * _dsScaleW + scaleState; + dst += (r >> 8); + cnt -= (r >> 8); + scaleState = r & 0xFF; + } + } else if (scaleState) { + (this->*_dsPlot)(dst++, c); + scaleState -= 0x100; + cnt--; + } + } while (cnt > 0); + + cnt = -1; +} + +void Screen::drawShapeProcessLineScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState) { + int c = 0; + + do { + if ((scaleState & 0x8000) || !(scaleState & 0xFF00)) { + c = *src++; + _dsTmpWidth--; + if (c) { + scaleState += _dsScaleW; + } else { + _dsTmpWidth++; + c = *src++; + _dsTmpWidth -= c; + int r = c * _dsScaleW + scaleState; + dst -= (r >> 8); + cnt -= (r >> 8); + scaleState = r & 0xFF; + } + } else { + (this->*_dsPlot)(dst--, c); + scaleState -= 0x100; + cnt--; + } + } while (cnt > 0); + + cnt = -1; +} + +void Screen::drawShapePlotType0(uint8 *dst, uint8 cmd) { + *dst = cmd; +} + +void Screen::drawShapePlotType1(uint8 *dst, uint8 cmd) { + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType3_7(uint8 *dst, uint8 cmd) { + cmd = *dst; + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType4(uint8 *dst, uint8 cmd) { + *dst = _dsColorTable[cmd]; +} + +void Screen::drawShapePlotType5(uint8 *dst, uint8 cmd) { + cmd = _dsColorTable[cmd]; + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType6(uint8 *dst, uint8 cmd) { + int t = _drawShapeVar4 + _drawShapeVar5; + if (t & 0xFF00) { + cmd = dst[_drawShapeVar3]; + t &= 0xFF; + } else { + cmd = _dsColorTable[cmd]; + } + + _drawShapeVar4 = t; + *dst = cmd; +} + +void Screen::drawShapePlotType8(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + if (_dsDrawLayer < t) + cmd = _shapePages[1][relOffs]; + + *dst = cmd; +} + +void Screen::drawShapePlotType9(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + if (_dsDrawLayer < t) { + cmd = _shapePages[1][relOffs]; + } else { + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + } + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType11_15(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + + if (_dsDrawLayer < t) { + cmd = _shapePages[1][relOffs]; + } else { + cmd = *dst; + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + } + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType12(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + if (_dsDrawLayer < t) { + cmd = _shapePages[1][relOffs]; + } else { + cmd = _dsColorTable[cmd]; + } + + *dst = cmd; +} + +void Screen::drawShapePlotType13(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + if (_dsDrawLayer < t) { + cmd = _shapePages[1][relOffs]; + } else { + cmd = _dsColorTable[cmd]; + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + } + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType14(uint8 *dst, uint8 cmd) { + uint32 relOffs = dst - _dsDstPage; + int t = (_shapePages[0][relOffs] & 0x7F) & 0x87; + if (_dsDrawLayer < t) { + cmd = _shapePages[1][relOffs]; + } else { + t = _drawShapeVar4 + _drawShapeVar5; + if (t & 0xFF00) { + cmd = dst[_drawShapeVar3]; + t &= 0xFF; + } else { + cmd = _dsColorTable[cmd]; + } + } + + _drawShapeVar4 = t; + *dst = cmd; +} + +void Screen::drawShapePlotType16(uint8 *dst, uint8 cmd) { + uint8 tOffs = _dsTransparencyTable1[cmd]; + if (!(tOffs & 0x80)) + cmd = _dsTransparencyTable2[tOffs << 8 | *dst]; + *dst = cmd; +} + +void Screen::drawShapePlotType20(uint8 *dst, uint8 cmd) { + cmd = _dsColorTable[cmd]; + uint8 tOffs = _dsTransparencyTable1[cmd]; + if (!(tOffs & 0x80)) + cmd = _dsTransparencyTable2[tOffs << 8 | *dst]; + + *dst = cmd; +} + +void Screen::drawShapePlotType21(uint8 *dst, uint8 cmd) { + cmd = _dsColorTable[cmd]; + uint8 tOffs = _dsTransparencyTable1[cmd]; + if (!(tOffs & 0x80)) + cmd = _dsTransparencyTable2[tOffs << 8 | *dst]; + + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType33(uint8 *dst, uint8 cmd) { + if (cmd == 255) { + *dst = _dsBackgroundFadingTable[*dst]; + } else { + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + if (cmd) + *dst = cmd; + } +} + +void Screen::drawShapePlotType37(uint8 *dst, uint8 cmd) { + cmd = _dsColorTable[cmd]; + + if (cmd == 255) { + cmd = _dsBackgroundFadingTable[*dst]; + } else { + for (int i = 0; i < _dsShapeFadingLevel; ++i) + cmd = _dsShapeFadingTable[cmd]; + } + + if (cmd) + *dst = cmd; +} + +void Screen::drawShapePlotType48(uint8 *dst, uint8 cmd) { + uint8 offs = _dsTransparencyTable1[cmd]; + if (!(offs & 0x80)) + cmd = _dsTransparencyTable2[(offs << 8) | *dst]; + *dst = cmd; +} + +void Screen::drawShapePlotType52(uint8 *dst, uint8 cmd) { + cmd = _dsColorTable[cmd]; + uint8 offs = _dsTransparencyTable1[cmd]; + + if (!(offs & 0x80)) + cmd = _dsTransparencyTable2[(offs << 8) | *dst]; + + *dst = cmd; +} + +void Screen::decodeFrame1(const uint8 *src, uint8 *dst, uint32 size) { + const uint8 *dstEnd = dst + size; + + struct Pattern { + const uint8 *pos; + uint16 len; + }; + + Pattern *patterns = new Pattern[3840]; + uint16 numPatterns = 0; + uint8 nib = 0; + + uint16 code = decodeEGAGetCode(src, nib); + uint8 last = code & 0xFF; + + uint8 *dstPrev = dst; + uint16 count = 1; + uint16 countPrev = 1; + + *dst++ = last; + + while (dst < dstEnd) { + code = decodeEGAGetCode(src, nib); + uint8 cmd = code >> 8; + + if (cmd--) { + code = (cmd << 8) | (code & 0xFF); + uint8 *tmpDst = dst; + + if (code < numPatterns) { + const uint8 *tmpSrc = patterns[code].pos; + countPrev = patterns[code].len; + last = *tmpSrc; + for (int i = 0; i < countPrev; i++) + *dst++ = *tmpSrc++; + + } else { + const uint8 *tmpSrc = dstPrev; + count = countPrev; + for (int i = 0; i < countPrev; i++) + *dst++ = *tmpSrc++; + *dst++ = last; + countPrev++; + } + + if (numPatterns < 3840) { + patterns[numPatterns].pos = dstPrev; + patterns[numPatterns++].len = ++count; + } + + dstPrev = tmpDst; + count = countPrev; + + } else { + *dst++ = last = (code & 0xFF); + + if (numPatterns < 3840) { + patterns[numPatterns].pos = dstPrev; + patterns[numPatterns++].len = ++count; + } + + dstPrev = dst - 1; + count = 1; + countPrev = 1; + } + } + delete[] patterns; +} + +uint16 Screen::decodeEGAGetCode(const uint8 *&pos, uint8 &nib) { + uint16 res = READ_BE_UINT16(pos++); + if ((++nib) & 1) { + res >>= 4; + } else { + pos++; + res &= 0xFFF; + } + return res; +} + +void Screen::decodeFrame3(const uint8 *src, uint8 *dst, uint32 size) { + const uint8 *dstEnd = dst + size; + while (dst < dstEnd) { + int8 code = *src++; + if (code == 0) { + uint16 sz = READ_BE_UINT16(src); + src += 2; + memset(dst, *src++, sz); + dst += sz; + } else if (code < 0) { + memset(dst, *src++, -code); + dst -= code; + } else { + memcpy(dst, src, code); + dst += code; + src += code; + } + } +} + +uint Screen::decodeFrame4(const uint8 *src, uint8 *dst, uint32 dstSize) { + uint8 *dstOrig = dst; + uint8 *dstEnd = dst + dstSize; + while (1) { + int count = dstEnd - dst; + if (count == 0) + break; + + uint8 code = *src++; + if (!(code & 0x80)) { // 8th bit isn't set + int len = MIN(count, (code >> 4) + 3); //upper half of code is the length + int offs = ((code & 0xF) << 8) | *src++; //lower half of code as byte 2 of offset. + const uint8 *dstOffs = dst - offs; + while (len--) + *dst++ = *dstOffs++; + } else if (code & 0x40) { // 7th bit is set + int len = (code & 0x3F) + 3; + if (code == 0xFE) { + len = READ_LE_UINT16(src); src += 2; + if (len > count) + len = count; + + memset(dst, *src++, len); dst += len; + } else { + if (code == 0xFF) { + len = READ_LE_UINT16(src); + src += 2; + } + + int offs = READ_LE_UINT16(src); src += 2; + if (len > count) + len = count; + + const uint8 *dstOffs = dstOrig + offs; + while (len--) + *dst++ = *dstOffs++; + } + } else if (code != 0x80) { // not just the 8th bit set. + //Copy some bytes from source to dest. + int len = MIN(count, code & 0x3F); + while (len--) + *dst++ = *src++; + } else { + break; + } + } + return dst - dstOrig; +} + +void Screen::decodeFrameDelta(uint8 *dst, const uint8 *src, bool noXor) { + if (noXor) + wrapped_decodeFrameDelta(dst, src); + else + wrapped_decodeFrameDelta(dst, src); +} + +template +void Screen::wrapped_decodeFrameDelta(uint8 *dst, const uint8 *src) { + while (1) { + uint8 code = *src++; + if (code == 0) { + uint8 len = *src++; + code = *src++; + while (len--) { + if (noXor) + *dst++ = code; + else + *dst++ ^= code; + } + } else if (code & 0x80) { + code -= 0x80; + if (code != 0) { + dst += code; + } else { + uint16 subcode = READ_LE_UINT16(src); src += 2; + if (subcode == 0) { + break; + } else if (subcode & 0x8000) { + subcode -= 0x8000; + if (subcode & 0x4000) { + uint16 len = subcode - 0x4000; + code = *src++; + while (len--) { + if (noXor) + *dst++ = code; + else + *dst++ ^= code; + } + } else { + while (subcode--) { + if (noXor) + *dst++ = *src++; + else + *dst++ ^= *src++; + } + } + } else { + dst += subcode; + } + } + } else { + while (code--) { + if (noXor) + *dst++ = *src++; + else + *dst++ ^= *src++; + } + } + } +} + +void Screen::decodeFrameDeltaPage(uint8 *dst, const uint8 *src, int pitch, bool noXor) { + if (noXor) + wrapped_decodeFrameDeltaPage(dst, src, pitch); + else + wrapped_decodeFrameDeltaPage(dst, src, pitch); +} + +void Screen::convertAmigaGfx(uint8 *data, int w, int h, int depth, bool wsa, int bytesPerPlane) { + const int planeWidth = (bytesPerPlane == -1) ? (w + 7) / 8 : bytesPerPlane; + const int planeSize = planeWidth * h; + const uint imageSize = planeSize * depth; + + // Our static buffer which holds the plane data. We need this + // because the "data" pointer is both source and destination pointer. + // The buffer has enough space to fit the AMIGA MSC files, which are + // the biggest graphics files found in the AMIGA version. + static uint8 temp[40320]; + assert(imageSize <= sizeof(temp)); + + // WSA files store their graphics data in a little different format, than + // the usual AMIGA graphics format used in BitMaps. Thus we need to do + // some special handling for them here. Means we convert them into + // the usual format. + // + // TODO: We might think of moving this conversion into the WSAMovieAmiga + // class. + if (wsa) { + const byte *src = data; + for (int y = 0; y < h; ++y) { + for (int x = 0; x < planeWidth; ++x) + for (int i = 0; i < depth; ++i) + temp[y * planeWidth + x + planeSize * i] = *src++; + } + } else { + memcpy(temp, data, imageSize); + } + + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + const int bytePos = x / 8 + y * planeWidth; + const int bitPos = 7 - (x & 7); // x & 7 == x % 8 + + byte col = 0; + + for (int i = 0; i < depth; ++i) + col |= ((temp[bytePos + planeSize * i] >> bitPos) & 1) << i; + + *data++ = col; + } + } +} + +void Screen::convertAmigaMsc(uint8 *data) { + // MSC files are always 320x144, thus we can safely assume + // this to be correct. Also they contain 7 planes instead + // of the normal 5 planes, which is used in 32 color mode. + // The need for 7 planes can be explained, because the MSC + // files have 6 bits for the layer number (bits 1 to 6) + // and one bit for the "blocked" flag (bit 0), and every + // plane contains one bit per pixel. + convertAmigaGfx(data, 320, 144, 7); + + // We need to do some post conversion, since + // the AMIGA MSC format is different from the DOS + // one we use internally for our code.That is even + // after converting it from the AMIGA plane based + // approach to one byte per pixel approach. + for (int i = 0; i < 320 * 144; ++i) { + // The lowest bit indicates, whether the position + // is walkable or not. If the bit is set, the + // position is walkable, elsewise it is blocked. + if (data[i] & 1) + data[i] &= 0xFE; + else + data[i] |= 0x80; + + // The graphics layer for the pixel is saved + // in the following format: + // The highest bit set indicates the number of + // the graphics layer. We count the first + // bit as 0 here, thus we need to add one, + // to get the correct number. + // + // Funnily since the first bit (bit 0) is + // resevered for testing whether the position + // is walkable or not, there is no possibility + // for layer 1 to be present. + int layer = 0; + for (int k = 0; k < 7; ++k) + if (data[i] & (1 << k)) + layer = k + 1; + + data[i] &= 0x80; + data[i] |= layer; + } +} + +template +void Screen::wrapped_decodeFrameDeltaPage(uint8 *dst, const uint8 *src, int pitch) { + int count = 0; + uint8 *dstNext = dst; + while (1) { + uint8 code = *src++; + if (code == 0) { + uint8 len = *src++; + code = *src++; + while (len--) { + if (noXor) + *dst++ = code; + else + *dst++ ^= code; + + if (++count == pitch) { + count = 0; + dstNext += SCREEN_W; + dst = dstNext; + } + } + } else if (code & 0x80) { + code -= 0x80; + if (code != 0) { + dst += code; + + count += code; + while (count >= pitch) { + count -= pitch; + dstNext += SCREEN_W; + dst = dstNext + count; + } + } else { + uint16 subcode = READ_LE_UINT16(src); src += 2; + if (subcode == 0) { + break; + } else if (subcode & 0x8000) { + subcode -= 0x8000; + if (subcode & 0x4000) { + uint16 len = subcode - 0x4000; + code = *src++; + while (len--) { + if (noXor) + *dst++ = code; + else + *dst++ ^= code; + + if (++count == pitch) { + count = 0; + dstNext += SCREEN_W; + dst = dstNext; + } + } + } else { + while (subcode--) { + if (noXor) + *dst++ = *src++; + else + *dst++ ^= *src++; + + if (++count == pitch) { + count = 0; + dstNext += SCREEN_W; + dst = dstNext; + } + } + } + } else { + dst += subcode; + + count += subcode; + while (count >= pitch) { + count -= pitch; + dstNext += SCREEN_W; + dst = dstNext + count; + } + + } + } + } else { + while (code--) { + if (noXor) + *dst++ = *src++; + else + *dst++ ^= *src++; + + if (++count == pitch) { + count = 0; + dstNext += SCREEN_W; + dst = dstNext; + } + } + } + } +} + +uint8 *Screen::encodeShape(int x, int y, int w, int h, int flags) { + uint8 *srcPtr = &_pagePtrs[_curPage][y * SCREEN_W + x]; + int16 shapeSize = 0; + uint8 *tmp = srcPtr; + int xpos = w; + + for (int i = h; i > 0; --i) { + uint8 *start = tmp; + shapeSize += w; + xpos = w; + while (xpos) { + uint8 value = *tmp++; + --xpos; + + if (!value) { + shapeSize += 2; + int16 curX = xpos; + bool skip = false; + + while (xpos) { + value = *tmp++; + --xpos; + + if (value) { + skip = true; + break; + } + } + + if (!skip) + ++curX; + + curX -= xpos; + shapeSize -= curX; + + while (curX > 0xFF) { + curX -= 0xFF; + shapeSize += 2; + } + } + } + + tmp = start + SCREEN_W; + } + + int16 shapeSize2 = shapeSize; + if (_vm->gameFlags().useAltShapeHeader) + shapeSize += 12; + else + shapeSize += 10; + + if (flags & 1) + shapeSize += 16; + + uint8 table[274]; + int tableIndex = 0; + + uint8 *newShape = 0; + newShape = new uint8[shapeSize+16]; + assert(newShape); + + byte *dst = newShape; + + if (_vm->gameFlags().useAltShapeHeader) + dst += 2; + + WRITE_LE_UINT16(dst, (flags & 3)); dst += 2; + *dst = h; dst += 1; + WRITE_LE_UINT16(dst, w); dst += 2; + *dst = h; dst += 1; + WRITE_LE_UINT16(dst, shapeSize); dst += 2; + WRITE_LE_UINT16(dst, shapeSize2); dst += 2; + + byte *src = srcPtr; + if (flags & 1) { + dst += 16; + memset(table, 0, sizeof(table)); + tableIndex = 1; + } + + for (int ypos = h; ypos > 0; --ypos) { + uint8 *srcBackUp = src; + xpos = w; + while (xpos) { + uint8 value = *src++; + if (value) { + if (flags & 1) { + if (!table[value]) { + if (tableIndex == 16) { + value = 1; + } else { + table[0x100+tableIndex] = value; + table[value] = tableIndex; + ++tableIndex; + value = table[value]; + } + } else { + value = table[value]; + } + } + --xpos; + *dst++ = value; + } else { + int16 temp = 1; + --xpos; + + while (xpos) { + if (*src) + break; + ++src; + ++temp; + --xpos; + } + + while (temp > 0xFF) { + *dst++ = 0; + *dst++ = 0xFF; + temp -= 0xFF; + } + + if (temp & 0xFF) { + *dst++ = 0; + *dst++ = temp & 0xFF; + } + } + } + src = srcBackUp + SCREEN_W; + } + + if (!(flags & 2)) { + if (shapeSize > _animBlockSize) { + dst = newShape; + if (_vm->gameFlags().useAltShapeHeader) + dst += 2; + + flags = READ_LE_UINT16(dst); + flags |= 2; + WRITE_LE_UINT16(dst, flags); + } else { + src = newShape; + if (_vm->gameFlags().useAltShapeHeader) + src += 2; + if (flags & 1) + src += 16; + + src += 10; + uint8 *shapePtrBackUp = src; + dst = _animBlockPtr; + memcpy(dst, src, shapeSize2); + + int16 size = encodeShapeAndCalculateSize(_animBlockPtr, shapePtrBackUp, shapeSize2); + if (size > shapeSize2) { + shapeSize -= shapeSize2 - size; + uint8 *newShape2 = new uint8[shapeSize]; + assert(newShape2); + memcpy(newShape2, newShape, shapeSize); + delete[] newShape; + newShape = newShape2; + } else { + dst = shapePtrBackUp; + src = _animBlockPtr; + memcpy(dst, src, shapeSize2); + dst = newShape; + if (_vm->gameFlags().useAltShapeHeader) + dst += 2; + flags = READ_LE_UINT16(dst); + flags |= 2; + WRITE_LE_UINT16(dst, flags); + } + } + } + + dst = newShape; + if (_vm->gameFlags().useAltShapeHeader) + dst += 2; + WRITE_LE_UINT16((dst + 6), shapeSize); + + if (flags & 1) { + dst = newShape + 10; + if (_vm->gameFlags().useAltShapeHeader) + dst += 2; + src = &table[0x100]; + memcpy(dst, src, sizeof(uint8)*16); + } + + return newShape; +} + +int16 Screen::encodeShapeAndCalculateSize(uint8 *from, uint8 *to, int size_to) { + byte *fromPtrEnd = from + size_to; + bool skipPixel = true; + byte *tempPtr = 0; + byte *toPtr = to; + byte *fromPtr = from; + byte *toPtr2 = to; + + *to++ = 0x81; + *to++ = *from++; + + while (from < fromPtrEnd) { + byte *curToPtr = to; + to = fromPtr; + int size = 1; + + while (true) { + byte curPixel = *from; + if (curPixel == *(from+0x40)) { + byte *toBackUp = to; + to = from; + + for (int i = 0; i < (fromPtrEnd - from); ++i) { + if (*to++ != curPixel) + break; + } + --to; + uint16 diffSize = (to - from); + if (diffSize >= 0x41) { + skipPixel = false; + from = to; + to = curToPtr; + *to++ = 0xFE; + WRITE_LE_UINT16(to, diffSize); to += 2; + *to++ = curPixel; + curToPtr = to; + to = toBackUp; + continue; + } else { + to = toBackUp; + } + } + + bool breakLoop = false; + while (true) { + if ((from - to) == 0) { + breakLoop = true; + break; + } + for (int i = 0; i < (from - to); ++i) { + if (*to++ == curPixel) + break; + } + if (*(to-1) == curPixel) { + if (*(from+size-1) != *(to+size-2)) + continue; + + byte *fromBackUp = from; + byte *toBackUp = to; + --to; + const int checkSize = fromPtrEnd - from; + for (int i = 0; i < checkSize; ++i) { + if (*from++ != *to++) + break; + } + if (*(from - 1) == *(to - 1)) + ++to; + from = fromBackUp; + int temp = to - toBackUp; + to = toBackUp; + if (temp >= size) { + size = temp; + tempPtr = toBackUp - 1; + } + break; + } else { + breakLoop = true; + break; + } + } + + if (breakLoop) + break; + } + + to = curToPtr; + if (size > 2) { + uint16 word = 0; + if (size <= 0x0A) { + uint16 diffSize = from - tempPtr; + if (diffSize <= 0x0FFF) { + byte highByte = ((diffSize & 0xFF00) >> 8) + (((size & 0xFF) - 3) << 4); + word = ((diffSize & 0xFF) << 8) | highByte; + WRITE_LE_UINT16(to, word); to += 2; + from += size; + skipPixel = false; + continue; + } + } + + if (size > 0x40) { + *to++ = 0xFF; + WRITE_LE_UINT16(to, size); to += 2; + } else { + *to++ = ((size & 0xFF) - 3) | 0xC0; + } + + word = tempPtr - fromPtr; + WRITE_LE_UINT16(to, word); to += 2; + from += size; + skipPixel = false; + } else { + if (!skipPixel) { + toPtr2 = to; + *to++ = 0x80; + } + + if (*toPtr2 == 0xBF) { + toPtr2 = to; + *to++ = 0x80; + } + + ++(*toPtr2); + *to++ = *from++; + skipPixel = true; + } + } + *to++ = 0x80; + + return (to - toPtr); +} + +uint16 Screen::shade16bitColor(uint16 col) { + uint8 r = (col & 0x1f); + uint8 g = (col & 0x3E0) >> 5; + uint8 b = (col & 0x7C00) >> 10; + + r = (r > _16bitShadingLevel) ? r - _16bitShadingLevel : 0; + g = (g > _16bitShadingLevel) ? g - _16bitShadingLevel : 0; + b = (b > _16bitShadingLevel) ? b - _16bitShadingLevel : 0; + + return (b << 10) | (g << 5) | r; +} + +void Screen::hideMouse() { + ++_mouseLockCount; + CursorMan.showMouse(false); +} + +void Screen::showMouse() { + if (_mouseLockCount == 1) { + CursorMan.showMouse(true); + + // We need to call OSystem::updateScreen here, else the mouse cursor + // will only be visible on mouse movment. + _system->updateScreen(); + } + + if (_mouseLockCount > 0) + _mouseLockCount--; +} + + +bool Screen::isMouseVisible() const { + return _mouseLockCount == 0; +} + +void Screen::setShapePages(int page1, int page2, int minY, int maxY) { + _shapePages[0] = _pagePtrs[page1]; + _shapePages[1] = _pagePtrs[page2]; + _maskMinY = minY; + _maskMaxY = maxY; +} + +void Screen::setMouseCursor(int x, int y, const byte *shape) { + if (!shape) + return; + + if (_vm->gameFlags().useAltShapeHeader) + shape += 2; + + int mouseHeight = *(shape + 2); + int mouseWidth = (READ_LE_UINT16(shape + 3)) + 2; + + if (_vm->gameFlags().useAltShapeHeader) + shape -= 2; + + if (_vm->gameFlags().useHiRes) { + x <<= 1; + y <<= 1; + mouseWidth <<= 1; + mouseHeight <<= 1; + } + + uint8 *cursor = new uint8[mouseHeight * mouseWidth]; + fillRect(0, 0, mouseWidth, mouseHeight, _cursorColorKey, 8); + drawShape(8, shape, 0, 0, 0, 0); + + int xOffset = 0; + + if (_vm->gameFlags().useHiRes) { + xOffset = mouseWidth; + scale2x(getPagePtr(8) + mouseWidth, SCREEN_W, getPagePtr(8), SCREEN_W, mouseWidth, mouseHeight); + postProcessCursor(getPagePtr(8) + mouseWidth, mouseWidth, mouseHeight, SCREEN_W); + } else { + postProcessCursor(getPagePtr(8), mouseWidth, mouseHeight, SCREEN_W); + } + + CursorMan.showMouse(false); + copyRegionToBuffer(8, xOffset, 0, mouseWidth, mouseHeight, cursor); + CursorMan.replaceCursor(cursor, mouseWidth, mouseHeight, x, y, _cursorColorKey); + if (isMouseVisible()) + CursorMan.showMouse(true); + delete[] cursor; + + // makes sure that the cursor is drawn + // we do not use Screen::updateScreen here + // so we can be sure that changes to page 0 + // are NOT updated on the real screen here + _system->updateScreen(); +} + +Palette &Screen::getPalette(int num) { + assert(num >= 0 && (uint)num < _palettes.size()); + return *_palettes[num]; +} + +void Screen::copyPalette(const int dst, const int src) { + getPalette(dst).copy(getPalette(src)); +} + +byte Screen::getShapeFlag1(int x, int y) { + uint8 color = _shapePages[0][y * SCREEN_W + x]; + color &= 0x80; + color ^= 0x80; + + if (color & 0x80) + return 1; + return 0; +} + +byte Screen::getShapeFlag2(int x, int y) { + uint8 color = _shapePages[0][y * SCREEN_W + x]; + color &= 0x7F; + color &= 0x87; + return color; +} + +int Screen::getDrawLayer(int x, int y) { + int xpos = x - 8; + int ypos = y - 1; + int layer = 1; + + for (int curX = xpos; curX < xpos + 16; ++curX) { + int tempLayer = getShapeFlag2(curX, ypos); + + if (layer < tempLayer) + layer = tempLayer; + + if (layer >= 7) + return 7; + } + return layer; +} + +int Screen::getDrawLayer2(int x, int y, int height) { + int xpos = x - 8; + int ypos = y - 1; + int layer = 1; + + for (int useX = xpos; useX < xpos + 16; ++useX) { + for (int useY = ypos - height; useY < ypos; ++useY) { + int tempLayer = getShapeFlag2(useX, useY); + + if (tempLayer > layer) + layer = tempLayer; + + if (tempLayer >= 7) + return 7; + } + } + return layer; +} + + +int Screen::setNewShapeHeight(uint8 *shape, int height) { + if (_vm->gameFlags().useAltShapeHeader) + shape += 2; + + int oldHeight = shape[2]; + shape[2] = height; + return oldHeight; +} + +int Screen::resetShapeHeight(uint8 *shape) { + if (_vm->gameFlags().useAltShapeHeader) + shape += 2; + + int oldHeight = shape[2]; + shape[2] = shape[5]; + return oldHeight; +} + +void Screen::blockInRegion(int x, int y, int width, int height) { + assert(_shapePages[0]); + byte *toPtr = _shapePages[0] + (y * 320 + x); + for (int i = 0; i < height; ++i) { + byte *backUpTo = toPtr; + for (int i2 = 0; i2 < width; ++i2) + *toPtr++ &= 0x7F; + toPtr = (backUpTo + 320); + } +} + +void Screen::blockOutRegion(int x, int y, int width, int height) { + assert(_shapePages[0]); + byte *toPtr = _shapePages[0] + (y * 320 + x); + for (int i = 0; i < height; ++i) { + byte *backUpTo = toPtr; + for (int i2 = 0; i2 < width; ++i2) + *toPtr++ |= 0x80; + toPtr = (backUpTo + 320); + } +} + +void Screen::rectClip(int &x, int &y, int w, int h) { + if (x < 0) + x = 0; + else if (x + w >= 320) + x = 320 - w; + + if (y < 0) + y = 0; + else if (y + h >= 200) + y = 200 - h; +} + +void Screen::shakeScreen(int times) { + while (times--) { + // seems to be 1 line (320 pixels) offset in the original + // 4 looks more like dosbox though, maybe check this again + _system->setShakePos(4); + _system->updateScreen(); + _system->setShakePos(0); + _system->updateScreen(); + } +} + +void Screen::loadBitmap(const char *filename, int tempPage, int dstPage, Palette *pal, bool skip) { + uint32 fileSize; + uint8 *srcData = _vm->resource()->fileData(filename, &fileSize); + + if (!srcData) { + warning("couldn't load bitmap: '%s'", filename); + return; + } + + if (skip) + srcData += 4; + + const char *ext = filename + strlen(filename) - 3; + uint8 compType = srcData[2]; + uint32 imgSize = (_vm->game() == GI_KYRA2 && !scumm_stricmp(ext, "CMP")) ? READ_LE_UINT16(srcData) : READ_LE_UINT32(srcData + 4); + uint16 palSize = READ_LE_UINT16(srcData + 8); + + if (pal && palSize) + loadPalette(srcData + 10, *pal, palSize); + + uint8 *srcPtr = srcData + 10 + palSize; + uint8 *dstData = getPagePtr(dstPage); + memset(dstData, 0, _screenPageSize); + if (dstPage == 0 || tempPage == 0) + _forceFullUpdate = true; + + switch (compType) { + case 0: + memcpy(dstData, srcPtr, imgSize); + break; + case 1: + Screen::decodeFrame1(srcPtr, dstData, imgSize); + break; + case 3: + Screen::decodeFrame3(srcPtr, dstData, imgSize); + break; + case 4: + Screen::decodeFrame4(srcPtr, dstData, imgSize); + break; + default: + error("Unhandled bitmap compression %d", compType); + } + + if (_isAmiga) { + if (!scumm_stricmp(ext, "MSC")) + Screen::convertAmigaMsc(dstData); + else + Screen::convertAmigaGfx(dstData, 320, 200); + } + + if (skip) + srcData -= 4; + + delete[] srcData; +} + +bool Screen::loadPalette(const char *filename, Palette &pal) { + if (_renderMode == Common::kRenderCGA) + return true; + + Common::SeekableReadStream *stream = _vm->resource()->createReadStream(filename); + + if (!stream) + return false; + + debugC(3, kDebugLevelScreen, "Screen::loadPalette('%s', %p)", filename, (const void *)&pal); + + const int maxCols = _16bitPalette ? 256 : pal.getNumColors(); + int numCols = 0; + + if (_isAmiga) { + numCols = stream->size() / Palette::kAmigaBytesPerColor; + pal.loadAmigaPalette(*stream, 0, MIN(maxCols, numCols)); + } else if (_vm->gameFlags().platform == Common::kPlatformPC98 && _use16ColorMode) { + numCols = stream->size() / Palette::kPC98BytesPerColor; + pal.loadPC98Palette(*stream, 0, MIN(maxCols, numCols)); + } else if (_renderMode == Common::kRenderEGA) { + numCols = stream->size(); + // There aren't any 16 color EGA palette files. So this shouldn't ever get triggered. + assert (numCols != 16); + numCols /= Palette::kVGABytesPerColor; + pal.loadVGAPalette(*stream, 0, numCols); + } else { + if (_bytesPerPixel == 2) { + numCols = stream->size() / 2; + pal.loadHiColorPalette(*stream, 0, numCols); + } else if (!_16bitPalette) { + numCols = stream->size() / Palette::kVGABytesPerColor; + pal.loadVGAPalette(*stream, 0, MIN(maxCols, numCols)); + } else { + error("Screen::loadPalette(): Failed to load file '%s' with invalid size %d in HiColor mode", filename, stream->size()); + } + } + + if (numCols > maxCols) + warning("Palette file '%s' includes %d colors, but the target palette only support %d colors", filename, numCols, maxCols); + + delete stream; + return true; +} + +bool Screen::loadPaletteTable(const char *filename, int firstPalette) { + Common::SeekableReadStream *stream = _vm->resource()->createReadStream(filename); + + if (!stream) + return false; + + debugC(3, kDebugLevelScreen, "Screen::loadPaletteTable('%s', %d)", filename, firstPalette); + + if (_isAmiga) { + const int numColors = getPalette(firstPalette).getNumColors(); + const int palSize = getPalette(firstPalette).getNumColors() * Palette::kAmigaBytesPerColor; + const int numPals = stream->size() / palSize; + + for (int i = 0; i < numPals; ++i) + getPalette(i + firstPalette).loadAmigaPalette(*stream, 0, numColors); + } else { + const int numColors = getPalette(firstPalette).getNumColors(); + const int palSize = getPalette(firstPalette).getNumColors() * Palette::kVGABytesPerColor; + const int numPals = stream->size() / palSize; + + for (int i = 0; i < numPals; ++i) + getPalette(i + firstPalette).loadVGAPalette(*stream, 0, numColors); + } + + delete stream; + return true; +} + +void Screen::loadPalette(const byte *data, Palette &pal, int bytes) { + Common::MemoryReadStream stream(data, bytes, DisposeAfterUse::NO); + + if (_isAmiga) + pal.loadAmigaPalette(stream, 0, stream.size() / Palette::kAmigaBytesPerColor); + else if (_vm->gameFlags().platform == Common::kPlatformPC98 && _use16ColorMode) + pal.loadPC98Palette(stream, 0, stream.size() / Palette::kPC98BytesPerColor); + else if (_renderMode == Common::kRenderEGA) { + // EOB II checks the number of palette bytes to distinguish between real EGA palettes + // and normal palettes (which are used to generate a color map). + if (stream.size() == 16) + pal.loadEGAPalette(stream, 0, stream.size()); + else + pal.loadVGAPalette(stream, 0, stream.size() / Palette::kVGABytesPerColor); + } else + pal.loadVGAPalette(stream, 0, stream.size() / Palette::kVGABytesPerColor); +} + +// dirty rect handling + +void Screen::addDirtyRect(int x, int y, int w, int h) { + if (_dirtyRects.size() >= kMaxDirtyRects || _forceFullUpdate) { + _forceFullUpdate = true; + return; + } + + Common::Rect r(x, y, x + w, y + h); + + // Clip rectangle + r.clip(SCREEN_W, SCREEN_H); + + // If it is empty after clipping, we are done + if (r.isEmpty()) + return; + + // Check if the new rectangle is contained within another in the list + Common::List::iterator it; + for (it = _dirtyRects.begin(); it != _dirtyRects.end(); ) { + // If we find a rectangle which fully contains the new one, + // we can abort the search. + if (it->contains(r)) + return; + + // Conversely, if we find rectangles which are contained in + // the new one, we can remove them + if (r.contains(*it)) + it = _dirtyRects.erase(it); + else + ++it; + } + + // If we got here, we can safely add r to the list of dirty rects. + _dirtyRects.push_back(r); +} + +// overlay functions + +byte *Screen::getOverlayPtr(int page) { + if (page == 0 || page == 1) + return _sjisOverlayPtrs[1]; + else if (page == 2 || page == 3) + return _sjisOverlayPtrs[2]; + + if (_vm->game() == GI_KYRA2) { + if (page == 12 || page == 13) + return _sjisOverlayPtrs[3]; + } else if (_vm->game() == GI_LOL) { + if (page == 4 || page == 5) + return _sjisOverlayPtrs[3]; + if (page == 6 || page == 7) + return _sjisOverlayPtrs[4]; + if (page == 12 || page == 13) + return _sjisOverlayPtrs[5]; + } + + return 0; +} + +void Screen::clearOverlayPage(int page) { + byte *dst = getOverlayPtr(page); + if (!dst) + return; + memset(dst, _sjisInvisibleColor, SCREEN_OVL_SJIS_SIZE); +} + +void Screen::clearOverlayRect(int page, int x, int y, int w, int h) { + byte *dst = getOverlayPtr(page); + + if (!dst || w < 0 || h < 0) + return; + + x <<= 1; y <<= 1; + w <<= 1; h <<= 1; + + dst += y * 640 + x; + + if (w == 640 && h == 400) { + memset(dst, _sjisInvisibleColor, SCREEN_OVL_SJIS_SIZE); + } else { + while (h--) { + memset(dst, _sjisInvisibleColor, w); + dst += 640; + } + } +} + +void Screen::copyOverlayRegion(int x, int y, int x2, int y2, int w, int h, int srcPage, int dstPage) { + byte *dst = getOverlayPtr(dstPage); + const byte *src = getOverlayPtr(srcPage); + + if (!dst || !src) + return; + + x <<= 1; x2 <<= 1; + y <<= 1; y2 <<= 1; + w <<= 1; h <<= 1; + + if (w == 640 && h == 400) { + memcpy(dst, src, SCREEN_OVL_SJIS_SIZE); + } else { + dst += y2 * 640 + x2; + src += y * 640 + x; + + while (h--) { + for (x = 0; x < w; ++x) + memmove(dst, src, w); + dst += 640; + src += 640; + } + } +} + +void Screen::crossFadeRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage) { + if (srcPage > 13 || dstPage > 13) + error("Screen::crossFadeRegion(): attempting to use temp page as source or dest page."); + + hideMouse(); + + uint16 *wB = (uint16 *)_pagePtrs[14]; + uint8 *hB = _pagePtrs[14] + 640 * _bytesPerPixel; + + for (int i = 0; i < w; i++) + wB[i] = i; + + for (int i = 0; i < h; i++) + hB[i] = i; + + for (int i = 0; i < w; i++) + SWAP(wB[_vm->_rnd.getRandomNumberRng(0, w - 1)], wB[i]); + + for (int i = 0; i < h; i++) + SWAP(hB[_vm->_rnd.getRandomNumberRng(0, h - 1)], hB[i]); + + uint8 *s = _pagePtrs[srcPage]; + uint8 *d = _pagePtrs[dstPage]; + + for (int i = 0; i < h; i++) { + int iH = i; + uint32 end = _system->getMillis() + 3; + for (int ii = 0; ii < w; ii++) { + int sX = (x1 + wB[ii]); + int sY = (y1 + hB[iH]); + int dX = (x2 + wB[ii]); + int dY = (y2 + hB[iH]); + + if (++iH >= h) + iH = 0; + + if (_bytesPerPixel == 2) + ((uint16*)d)[dY * 320 + dX] = ((uint16*)s)[sY * 320 + sX]; + else + d[dY * 320 + dX] = s[sY * 320 + sX]; + addDirtyRect(dX, dY, 1, 1); + } + + // This tries to speed things up, to get similiar speeds as in DOSBox etc. + // We can't write single pixels directly into the video memory like the original did. + // We also (unlike the original) want to aim at similiar speeds for all platforms. + if (!(i % 10)) + updateScreen(); + + uint32 cur = _system->getMillis(); + if (end > cur) + _system->delayMillis(end - cur); + } + + updateScreen(); + showMouse(); +} + +#pragma mark - + +DOSFont::DOSFont() { + _data = _widthTable = _heightTable = 0; + _colorMap = 0; + _width = _height = _numGlyphs = 0; + _bitmapOffsets = 0; +} + +bool DOSFont::load(Common::SeekableReadStream &file) { + unload(); + + _data = new uint8[file.size()]; + assert(_data); + + file.read(_data, file.size()); + if (file.err()) + return false; + + const uint16 fontSig = READ_LE_UINT16(_data + 2); + + if (fontSig != 0x0500) { + warning("DOSFont: invalid font: %.04X)", fontSig); + return false; + } + + const uint16 descOffset = READ_LE_UINT16(_data + 4); + + _width = _data[descOffset + 5]; + _height = _data[descOffset + 4]; + _numGlyphs = _data[descOffset + 3] + 1; + + _bitmapOffsets = (uint16 *)(_data + READ_LE_UINT16(_data + 6)); + _widthTable = _data + READ_LE_UINT16(_data + 8); + _heightTable = _data + READ_LE_UINT16(_data + 12); + + for (int i = 0; i < _numGlyphs; ++i) + _bitmapOffsets[i] = READ_LE_UINT16(&_bitmapOffsets[i]); + + return true; +} + +int DOSFont::getCharWidth(uint16 c) const { + if (c >= _numGlyphs) + return 0; + return _widthTable[c]; +} + +void DOSFont::drawChar(uint16 c, byte *dst, int pitch, int) const { + if (c >= _numGlyphs) + return; + + if (!_bitmapOffsets[c]) + return; + + const uint8 *src = _data + _bitmapOffsets[c]; + const uint8 charWidth = _widthTable[c]; + + if (!charWidth) + return; + + pitch -= charWidth; + + uint8 charH1 = _heightTable[c * 2 + 0]; + uint8 charH2 = _heightTable[c * 2 + 1]; + uint8 charH0 = _height - (charH1 + charH2); + + while (charH1--) { + uint8 col = _colorMap[0]; + for (int i = 0; i < charWidth; ++i) { + if (col != 0) + *dst = col; + ++dst; + } + dst += pitch; + } + + while (charH2--) { + uint8 b = 0; + for (int i = 0; i < charWidth; ++i) { + uint8 col; + if (i & 1) { + col = _colorMap[b >> 4]; + } else { + b = *src++; + col = _colorMap[b & 0xF]; + } + if (col != 0) { + *dst = col; + } + ++dst; + } + dst += pitch; + } + + while (charH0--) { + uint8 col = _colorMap[0]; + for (int i = 0; i < charWidth; ++i) { + if (col != 0) + *dst = col; + ++dst; + } + dst += pitch; + } +} + +void DOSFont::unload() { + delete[] _data; + _data = _widthTable = _heightTable = 0; + _colorMap = 0; + _width = _height = _numGlyphs = 0; + _bitmapOffsets = 0; +} + + +AMIGAFont::AMIGAFont() { + _width = _height = 0; + memset(_chars, 0, sizeof(_chars)); +} + +bool AMIGAFont::load(Common::SeekableReadStream &file) { + const uint16 dataSize = file.readUint16BE(); + if (dataSize + 2 != file.size()) + return false; + + _width = file.readByte(); + _height = file.readByte(); + + // Read the character definition offset table + uint16 offsets[ARRAYSIZE(_chars)]; + for (int i = 0; i < ARRAYSIZE(_chars); ++i) + offsets[i] = file.readUint16BE() + 4; + + if (file.err()) + return false; + + for (int i = 0; i < ARRAYSIZE(_chars); ++i) { + file.seek(offsets[i], SEEK_SET); + + _chars[i].yOffset = file.readByte(); + _chars[i].xOffset = file.readByte(); + _chars[i].width = file.readByte(); + file.readByte(); // unused + + // If the y offset is 255, then the character + // does not have any bitmap representation + if (_chars[i].yOffset != 255) { + Character::Graphics &g = _chars[i].graphics; + + g.width = file.readUint16BE(); + g.height = file.readUint16BE(); + + int depth = file.readByte(); + int specialWidth = file.readByte(); + int flags = file.readByte(); + int bytesPerPlane = file.readByte(); + + assert(depth != 0 && specialWidth == 0 && flags == 0 && bytesPerPlane != 0); + + // Allocate a temporary buffer to store the plane data + const int planesSize = bytesPerPlane * g.height * depth; + uint8 *tempData = new uint8[MAX(g.width * g.height, planesSize)]; + assert(tempData); + + file.read(tempData, planesSize); + + // Convert the plane based graphics to our graphic format + Screen::convertAmigaGfx(tempData, g.width, g.height, depth, false, bytesPerPlane); + + // Create a buffer perfectly fitting the character + g.bitmap = new uint8[g.width * g.height]; + assert(g.bitmap); + + memcpy(g.bitmap, tempData, g.width * g.height); + delete[] tempData; + } + + if (file.err()) + return false; + } + + return !file.err(); +} + +int AMIGAFont::getCharWidth(uint16 c) const { + if (c >= 255) + return 0; + return _chars[c].width; +} + +void AMIGAFont::drawChar(uint16 c, byte *dst, int pitch, int) const { + if (c >= 255) + return; + + if (_chars[c].yOffset == 255) + return; + + dst += _chars[c].yOffset * pitch; + dst += _chars[c].xOffset; + + pitch -= _chars[c].graphics.width; + + const uint8 *src = _chars[c].graphics.bitmap; + assert(src); + + for (int y = 0; y < _chars[c].graphics.height; ++y) { + for (int x = 0; x < _chars[c].graphics.width; ++x) { + if (*src) + *dst = *src; + ++src; + ++dst; + } + + dst += pitch; + } +} + +void AMIGAFont::unload() { + _width = _height = 0; + for (int i = 0; i < ARRAYSIZE(_chars); ++i) + delete[] _chars[i].graphics.bitmap; + memset(_chars, 0, sizeof(_chars)); +} + +SJISFont::SJISFont(Graphics::FontSJIS *font, const uint8 invisColor, bool is16Color, bool drawOutline, bool fatPrint, int extraSpacing) + : _colorMap(0), _font(font), _invisColor(invisColor), _is16Color(is16Color), _drawOutline(drawOutline), _sjisWidthOffset(extraSpacing) { + assert(_font); + _font->setDrawingMode(_drawOutline ? Graphics::FontSJIS::kOutlineMode : Graphics::FontSJIS::kDefaultMode); + _font->toggleFatPrint(fatPrint); + _sjisWidth = _font->getMaxFontWidth() >> 1; + _fontHeight = _font->getFontHeight() >> 1; + _asciiWidth = _font->getCharWidth('a') >> 1; +} + +void SJISFont::unload() { + delete _font; + _font = 0; +} + +int SJISFont::getHeight() const { + return _fontHeight; +} + +int SJISFont::getWidth() const { + return _sjisWidth + _sjisWidthOffset; +} + +int SJISFont::getCharWidth(uint16 c) const { + if (c <= 0x7F || (c >= 0xA1 && c <= 0xDF)) + return _asciiWidth; + else + return _sjisWidth + _sjisWidthOffset; +} + +void SJISFont::setColorMap(const uint8 *src) { + _colorMap = src; + + if (!_is16Color) { + if (_colorMap[0] == _invisColor) + _font->setDrawingMode(Graphics::FontSJIS::kDefaultMode); + else + _font->setDrawingMode(_drawOutline ? Graphics::FontSJIS::kOutlineMode : Graphics::FontSJIS::kDefaultMode); + } +} + +void SJISFont::drawChar(uint16 c, byte *dst, int pitch, int) const { + uint8 color1, color2; + + if (_is16Color) { + // PC98 16 color games specify a color value which is for the + // PC98 text mode palette, thus we need to remap it. + color1 = ((_colorMap[1] >> 5) & 0x7) + 16; + color2 = ((_colorMap[0] >> 5) & 0x7) + 16; + } else { + color1 = _colorMap[1]; + color2 = _colorMap[0]; + } + + _font->drawChar(dst, c, 640, 1, color1, color2, 640, 400); +} + +#pragma mark - + +Palette::Palette(const int numColors) : _palData(0), _numColors(numColors) { + _palData = new uint8[numColors * 3]; + assert(_palData); + + memset(_palData, 0, numColors * 3); +} + +Palette::~Palette() { + delete[] _palData; + _palData = 0; +} + +void Palette::loadVGAPalette(Common::ReadStream &stream, int startIndex, int colors) { + assert(startIndex + colors <= _numColors); + + uint8 *pos = _palData + startIndex * 3; + for (int i = 0 ; i < colors * 3; i++) + *pos++ = stream.readByte() & 0x3F; +} + +void Palette::loadHiColorPalette(Common::ReadStream &stream, int startIndex, int colors) { + uint16 *pos = (uint16*)(_palData + startIndex * 2); + + Graphics::PixelFormat currentFormat = g_system->getScreenFormat(); + Graphics::PixelFormat originalFormat(2, 5, 5, 5, 0, 5, 10, 0, 0); + + for (int i = 0; i < colors; i++) { + uint8 r, g, b; + originalFormat.colorToRGB(stream.readUint16LE(), r, g, b); + *pos++ = currentFormat.RGBToColor(r, g, b); + } +} + +void Palette::loadEGAPalette(Common::ReadStream &stream, int startIndex, int colors) { + assert(startIndex + colors <= 16); + + uint8 *dst = _palData + startIndex * 3; + for (int i = 0; i < colors; i++) { + uint8 index = stream.readByte(); + assert(index < _egaNumColors); + memcpy(dst, &_egaColors[index * 3], 3); + dst += 3; + } +} + +void Palette::setCGAPalette(int palIndex, CGAIntensity intensity) { + assert(_numColors >= _cgaNumColors); + assert(!(palIndex & ~1)); + memcpy(_palData, _cgaColors[palIndex * 2 + intensity], _numColors * 3); +} + +void Palette::loadAmigaPalette(Common::ReadStream &stream, int startIndex, int colors) { + assert(startIndex + colors <= _numColors); + + for (int i = 0; i < colors; ++i) { + uint16 col = stream.readUint16BE(); + _palData[(i + startIndex) * 3 + 2] = ((col & 0xF) * 0x3F) / 0xF; col >>= 4; + _palData[(i + startIndex) * 3 + 1] = ((col & 0xF) * 0x3F) / 0xF; col >>= 4; + _palData[(i + startIndex) * 3 + 0] = ((col & 0xF) * 0x3F) / 0xF; col >>= 4; + } +} + +void Palette::loadPC98Palette(Common::ReadStream &stream, int startIndex, int colors) { + assert(startIndex + colors <= _numColors); + + for (int i = 0; i < colors; ++i) { + const byte g = stream.readByte(), r = stream.readByte(), b = stream.readByte(); + + _palData[(i + startIndex) * 3 + 0] = ((r & 0xF) * 0x3F) / 0xF; + _palData[(i + startIndex) * 3 + 1] = ((g & 0xF) * 0x3F) / 0xF; + _palData[(i + startIndex) * 3 + 2] = ((b & 0xF) * 0x3F) / 0xF; + } +} + +void Palette::clear() { + memset(_palData, 0, _numColors * 3); +} + +void Palette::fill(int firstCol, int numCols, uint8 value) { + assert(firstCol >= 0 && firstCol + numCols <= _numColors); + + memset(_palData + firstCol * 3, CLIP(value, 0, 63), numCols * 3); +} + +void Palette::copy(const Palette &source, int firstCol, int numCols, int dstStart) { + if (numCols == -1) + numCols = MIN(source.getNumColors(), _numColors) - firstCol; + if (dstStart == -1) + dstStart = firstCol; + + assert(numCols >= 0 && numCols <= _numColors); + assert(firstCol >= 0 && firstCol <= source.getNumColors()); + assert(dstStart >= 0 && dstStart + numCols <= _numColors); + + memmove(_palData + dstStart * 3, source._palData + firstCol * 3, numCols * 3); +} + +void Palette::copy(const uint8 *source, int firstCol, int numCols, int dstStart) { + if (dstStart == -1) + dstStart = firstCol; + + assert(numCols >= 0 && numCols <= _numColors); + assert(firstCol >= 0); + assert(dstStart >= 0 && dstStart + numCols <= _numColors); + + memmove(_palData + dstStart * 3, source + firstCol * 3, numCols * 3); +} + +uint8 *Palette::fetchRealPalette() const { + uint8 *buffer = new uint8[_numColors * 3]; + assert(buffer); + + uint8 *dst = buffer; + const uint8 *palData = _palData; + + for (int i = 0; i < _numColors; ++i) { + dst[0] = (palData[0] << 2) | (palData[0] & 3); + dst[1] = (palData[1] << 2) | (palData[1] & 3); + dst[2] = (palData[2] << 2) | (palData[2] & 3); + + dst += 3; + palData += 3; + } + + return buffer; +} + +const uint8 Palette::_egaColors[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0x00, 0xAA, 0xAA, + 0xAA, 0x00, 0x00, 0xAA, 0x00, 0xAA, 0xAA, 0x55, 0x00, 0xAA, 0xAA, 0xAA, + 0x55, 0x55, 0x55, 0x55, 0x55, 0xFF, 0x55, 0xFF, 0x55, 0x55, 0xFF, 0xFF, + 0xFF, 0x55, 0x55, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF +}; + +const int Palette::_egaNumColors = ARRAYSIZE(_egaColors) / 3; + +const uint8 Palette::_cgaColors[4][12] = { + { 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x2A, 0x00, 0x00, 0x2A, 0x15, 0x00 }, + { 0x00, 0x00, 0x00, 0x15, 0x3F, 0x15, 0x3F, 0x15, 0x15, 0x3F, 0x3F, 0x15 }, + { 0x00, 0x00, 0x00, 0x00, 0x2A, 0x2A, 0x2A, 0x00, 0x2A, 0x2A, 0x2A, 0x2A }, + { 0x00, 0x00, 0x00, 0x15, 0x3F, 0x3F, 0x3F, 0x15, 0x3F, 0x3F, 0x3F, 0x3F } +}; + +const int Palette::_cgaNumColors = ARRAYSIZE(_cgaColors[0]) / 3; + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen.h b/engines/kyra/graphics/screen.h new file mode 100644 index 0000000000..44113e4372 --- /dev/null +++ b/engines/kyra/graphics/screen.h @@ -0,0 +1,736 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_H +#define KYRA_SCREEN_H + +#include "common/util.h" +#include "common/func.h" +#include "common/list.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/rendermode.h" +#include "common/stream.h" + +class OSystem; + +namespace Graphics { +class FontSJIS; +} // End of namespace Graphics + +namespace Kyra { + +typedef Common::Functor0 UpdateFunctor; + +class KyraEngine_v1; +class Screen; + +struct ScreenDim { + uint16 sx; + uint16 sy; + uint16 w; + uint16 h; + uint16 unk8; + uint16 unkA; + uint16 unkC; + uint16 unkE; +}; + +/** + * A class that handles KYRA fonts. + */ +class Font { +public: + virtual ~Font() {} + + /** + * Tries to load a file from the given stream + */ + virtual bool load(Common::SeekableReadStream &file) = 0; + + /** + * Whether the font draws on the overlay. + */ + virtual bool usesOverlay() const { return false; } + + /** + * The font height. + */ + virtual int getHeight() const = 0; + + /** + * The font width, this is the maximal character + * width. + */ + virtual int getWidth() const = 0; + + /** + * Gets the width of a specific character. + */ + virtual int getCharWidth(uint16 c) const = 0; + + /** + * Sets a text palette map. The map contains 16 entries. + */ + virtual void setColorMap(const uint8 *src) = 0; + + /** + * Sets a text 16bit palette map. Only used in in EOB II FM-Towns. The map contains 2 entries. + */ + virtual void set16bitColorMap(const uint16 *src) {} + + /** + * Draws a specific character. + * + * TODO/FIXME: Replace this with a nicer API. Currently + * the user has to assure that the character fits. + * We use this API, since it's hard to assure dirty rect + * handling from outside Screen. + */ + virtual void drawChar(uint16 c, byte *dst, int pitch, int bpp) const = 0; +}; + +/** + * Implementation of the Font interface for DOS fonts. + * + * TODO: Clean up the implementation. For example we might be able + * to not to keep the whole font in memory. + */ +class DOSFont : public Font { +public: + DOSFont(); + ~DOSFont() { unload(); } + + bool load(Common::SeekableReadStream &file); + int getHeight() const { return _height; } + int getWidth() const { return _width; } + int getCharWidth(uint16 c) const; + void setColorMap(const uint8 *src) { _colorMap = src; } + void drawChar(uint16 c, byte *dst, int pitch, int) const; + +private: + void unload(); + + const uint8 *_colorMap; + + uint8 *_data; + + int _width, _height; + + int _numGlyphs; + + uint8 *_widthTable; + uint8 *_heightTable; + uint16 *_bitmapOffsets; +}; + +#ifdef ENABLE_EOB +/** +* Implementation of the Font interface for old DOS fonts used +* in EOB and EOB II. +* +*/ +class OldDOSFont : public Font { +public: + OldDOSFont(Common::RenderMode mode); + ~OldDOSFont(); + + bool load(Common::SeekableReadStream &file); + int getHeight() const { return _height; } + int getWidth() const { return _width; } + int getCharWidth(uint16 c) const; + void setColorMap(const uint8 *src) { _colorMap8bit = src; } + void set16bitColorMap(const uint16 *src) { _colorMap16bit = src; } + void drawChar(uint16 c, byte *dst, int pitch, int bpp) const; + +private: + void unload(); + + uint8 *_data; + uint16 *_bitmapOffsets; + + int _width, _height; + const uint8 *_colorMap8bit; + const uint16 *_colorMap16bit; + + int _numGlyphs; + + Common::RenderMode _renderMode; + + static uint16 *_cgaDitheringTable; + static int _numRef; +}; +#endif // ENABLE_EOB + +/** + * Implementation of the Font interface for AMIGA fonts. + */ +class AMIGAFont : public Font { +public: + AMIGAFont(); + ~AMIGAFont() { unload(); } + + bool load(Common::SeekableReadStream &file); + int getHeight() const { return _height; } + int getWidth() const { return _width; } + int getCharWidth(uint16 c) const; + void setColorMap(const uint8 *src) {} + void drawChar(uint16 c, byte *dst, int pitch, int) const; + +private: + void unload(); + + int _width, _height; + + struct Character { + uint8 yOffset, xOffset, width; + + struct Graphics { + uint16 width, height; + uint8 *bitmap; + } graphics; + }; + + Character _chars[255]; +}; + +/** + * Implementation of the Font interface for FM-Towns/PC98 fonts + */ +class SJISFont : public Font { +public: + SJISFont(Graphics::FontSJIS *font, const uint8 invisColor, bool is16Color, bool drawOutline, bool fatPrint, int extraSpacing); + virtual ~SJISFont() { unload(); } + + virtual bool usesOverlay() const { return true; } + + bool load(Common::SeekableReadStream &) { return true; } + int getHeight() const; + int getWidth() const; + int getCharWidth(uint16 c) const; + void setColorMap(const uint8 *src); + virtual void drawChar(uint16 c, byte *dst, int pitch, int) const; + +protected: + void unload(); + + const uint8 *_colorMap; + Graphics::FontSJIS *_font; + int _sjisWidth, _asciiWidth; + int _fontHeight; + const bool _drawOutline; + +private: + const uint8 _invisColor; + const bool _is16Color; + // We use this for cases where the font width returned by getWidth() or getCharWidth() does not match the original. + // The original Japanese game versions use hard coded sjis font widths of 8 or 9. However, this does not necessarily + // depend on whether an outline is used or not (neither LOL/PC-9801 nor LOL/FM-TOWNS use an outline, but the first + // version uses a font width of 8 where the latter uses a font width of 9). + const int _sjisWidthOffset; +}; + +/** + * A class that manages KYRA palettes. + * + * This class stores the palette data as VGA RGB internally. + */ +class Palette { +public: + Palette(const int numColors); + ~Palette(); + + enum { + kVGABytesPerColor = 3, + kPC98BytesPerColor = 3, + kAmigaBytesPerColor = 2 + }; + + /** + * Load a VGA palette from the given stream. + */ + void loadVGAPalette(Common::ReadStream &stream, int startIndex, int colors); + + /** + * Load a HiColor palette from the given stream. + */ + void loadHiColorPalette(Common::ReadStream &stream, int startIndex, int colors); + + /** + * Load a EGA palette from the given stream. + */ + void loadEGAPalette(Common::ReadStream &stream, int startIndex, int colors); + + /** + * Set default CGA palette. We only need the cyan/magenta/grey mode. + */ + enum CGAIntensity { + kIntensityLow = 0, + kIntensityHigh = 1 + }; + + void setCGAPalette(int palIndex, CGAIntensity intensity); + + /** + * Load a AMIGA palette from the given stream. + */ + void loadAmigaPalette(Common::ReadStream &stream, int startIndex, int colors); + + /** + * Load a PC98 16 color palette from the given stream. + */ + void loadPC98Palette(Common::ReadStream &stream, int startIndex, int colors); + + /** + * Return the number of colors this palette manages. + */ + int getNumColors() const { return _numColors; } + + /** + * Set all palette colors to black. + */ + void clear(); + + /** + * Fill the given indexes with the given component value. + * + * @param firstCol the first color, which should be overwritten. + * @param numCols number of colors, which schould be overwritten. + * @param value color component value, which should be stored. + */ + void fill(int firstCol, int numCols, uint8 value); + + /** + * Copy data from another palette. + * + * @param source palette to copy data from. + * @param firstCol the first color of the source which should be copied. + * @param numCols number of colors, which should be copied. -1 all remaining colors. + * @param dstStart the first color, which should be ovewritten. If -1 firstCol will be used as start. + */ + void copy(const Palette &source, int firstCol = 0, int numCols = -1, int dstStart = -1); + + /** + * Copy data from a raw VGA palette. + * + * @param source source buffer + * @param firstCol the first color of the source which should be copied. + * @param numCols number of colors, which should be copied. + * @param dstStart the first color, which should be ovewritten. If -1 firstCol will be used as start. + */ + void copy(const uint8 *source, int firstCol, int numCols, int dstStart = -1); + + /** + * Fetch a RGB palette. + * + * @return a pointer to the RGB palette data, the client must delete[] it. + */ + uint8 *fetchRealPalette() const; + + //XXX + uint8 &operator[](const int index) { + assert(index >= 0 && index <= _numColors * 3); + return _palData[index]; + } + + const uint8 &operator[](const int index) const { + assert(index >= 0 && index <= _numColors * 3); + return _palData[index]; + } + + /** + * Gets raw access to the palette. + * + * TODO: Get rid of this. + */ + uint8 *getData() { return _palData; } + const uint8 *getData() const { return _palData; } + +private: + uint8 *_palData; + const int _numColors; + + static const uint8 _egaColors[]; + static const int _egaNumColors; + static const uint8 _cgaColors[4][12]; + static const int _cgaNumColors; +}; + +class Screen { +public: + enum { + SCREEN_W = 320, + SCREEN_H = 200, + SCREEN_PAGE_SIZE = 320 * 200 + 1024, + SCREEN_OVL_SJIS_SIZE = 640 * 400, + SCREEN_PAGE_NUM = 16, + SCREEN_OVLS_NUM = 6 + }; + + enum CopyRegionFlags { + CR_NO_P_CHECK = 0x01 + }; + + enum DrawShapeFlags { + DSF_X_FLIPPED = 0x01, + DSF_Y_FLIPPED = 0x02, + DSF_SCALE = 0x04, + DSF_WND_COORDS = 0x10, + DSF_CENTER = 0x20, + + DSF_SHAPE_FADING = 0x100, + DSF_TRANSPARENCY = 0x1000, + DSF_BACKGROUND_FADING = 0x2000, + DSF_CUSTOM_PALETTE = 0x8000 + }; + + enum FontId { + FID_6_FNT = 0, + FID_8_FNT, + FID_9_FNT, + FID_CRED6_FNT, + FID_CRED8_FNT, + FID_BOOKFONT_FNT, + FID_GOLDFONT_FNT, + FID_INTRO_FNT, + FID_SJIS_FNT, + FID_SJIS_LARGE_FNT, + FID_SJIS_SMALL_FNT, + FID_NUM + }; + + Screen(KyraEngine_v1 *vm, OSystem *system, const ScreenDim *dimTable, const int dimTableSize); + virtual ~Screen(); + + // init + virtual bool init(); + virtual void setResolution(); + virtual void enableHiColorMode(bool enabled); + + void updateScreen(); + + // debug functions + bool queryScreenDebug() const { return _debugEnabled; } + bool enableScreenDebug(bool enable); + + // page cur. functions + int setCurPage(int pageNum); + void clearCurPage(); + + void copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src, + int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2); + + // page 0 functions + void copyToPage0(int y, int h, uint8 page, uint8 *seqBuf); + void shakeScreen(int times); + + // page functions + void copyRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage, int flags=0); + void copyPage(uint8 srcPage, uint8 dstPage); + + void copyRegionToBuffer(int pageNum, int x, int y, int w, int h, uint8 *dest); + void copyBlockToPage(int pageNum, int x, int y, int w, int h, const uint8 *src); + + void shuffleScreen(int sx, int sy, int w, int h, int srcPage, int dstPage, int ticks, bool transparent); + void fillRect(int x1, int y1, int x2, int y2, uint8 color, int pageNum = -1, bool xored = false); + + void clearPage(int pageNum); + + int getPagePixel(int pageNum, int x, int y); + void setPagePixel(int pageNum, int x, int y, uint8 color); + + const uint8 *getCPagePtr(int pageNum) const; + uint8 *getPageRect(int pageNum, int x, int y, int w, int h); + + // palette handling + void fadeFromBlack(int delay=0x54, const UpdateFunctor *upFunc = 0); + void fadeToBlack(int delay=0x54, const UpdateFunctor *upFunc = 0); + + virtual void fadePalette(const Palette &pal, int delay, const UpdateFunctor *upFunc = 0); + virtual void getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff); + virtual int fadePalStep(const Palette &pal, int diff); + + void setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue); + virtual void setScreenPalette(const Palette &pal); + + // AMIGA version only + bool isInterfacePaletteEnabled() const { return _interfacePaletteEnabled; } + void enableInterfacePalette(bool e); + void setInterfacePalette(const Palette &pal, uint8 r, uint8 g, uint8 b); + + virtual void getRealPalette(int num, uint8 *dst); + Palette &getPalette(int num); + void copyPalette(const int dst, const int src); + + // gui specific (processing on _curPage) + void drawLine(bool vertical, int x, int y, int length, int color); + void drawClippedLine(int x1, int y1, int x2, int y2, int color); + virtual void drawShadedBox(int x1, int y1, int x2, int y2, int color1, int color2); + void drawBox(int x1, int y1, int x2, int y2, int color); + + // font/text handling + virtual bool loadFont(FontId fontId, const char *filename); + FontId setFont(FontId fontId); + + int getFontHeight() const; + int getFontWidth() const; + + int getCharWidth(uint16 c) const; + int getTextWidth(const char *str); + + void printText(const char *str, int x, int y, uint8 color1, uint8 color2); + + virtual void setTextColorMap(const uint8 *cmap) = 0; + void setTextColor(const uint8 *cmap, int a, int b); + void setTextColor16bit(const uint16 *cmap16); + + const ScreenDim *getScreenDim(int dim) const; + void modifyScreenDim(int dim, int x, int y, int w, int h); + int screenDimTableCount() const { return _dimTableCount; } + + void setScreenDim(int dim); + int curDimIndex() const { return _curDimIndex; } + + const ScreenDim *_curDim; + + // shape handling + uint8 *encodeShape(int x, int y, int w, int h, int flags); + + int setNewShapeHeight(uint8 *shape, int height); + int resetShapeHeight(uint8 *shape); + + virtual void drawShape(uint8 pageNum, const uint8 *shapeData, int x, int y, int sd, int flags, ...); + + // mouse handling + void hideMouse(); + void showMouse(); + bool isMouseVisible() const; + virtual void setMouseCursor(int x, int y, const byte *shape); + + // rect handling + virtual int getRectSize(int w, int h) = 0; + + void rectClip(int &x, int &y, int w, int h); + + // misc + void loadBitmap(const char *filename, int tempPage, int dstPage, Palette *pal, bool skip=false); + + virtual bool loadPalette(const char *filename, Palette &pal); + bool loadPaletteTable(const char *filename, int firstPalette); + virtual void loadPalette(const byte *data, Palette &pal, int bytes); + + void setAnimBlockPtr(int size); + + void setShapePages(int page1, int page2, int minY = -1, int maxY = 201); + + virtual byte getShapeFlag1(int x, int y); + virtual byte getShapeFlag2(int x, int y); + + virtual int getDrawLayer(int x, int y); + virtual int getDrawLayer2(int x, int y, int height); + + void blockInRegion(int x, int y, int width, int height); + void blockOutRegion(int x, int y, int width, int height); + + int _charWidth; + int _charOffset; + int _curPage; + uint8 *_shapePages[2]; + int _maskMinY, _maskMaxY; + FontId _currentFont; + + // decoding functions + static void decodeFrame1(const uint8 *src, uint8 *dst, uint32 size); + static uint16 decodeEGAGetCode(const uint8 *&pos, uint8 &nib); + + static void decodeFrame3(const uint8 *src, uint8 *dst, uint32 size); + static uint decodeFrame4(const uint8 *src, uint8 *dst, uint32 dstSize); + static void decodeFrameDelta(uint8 *dst, const uint8 *src, bool noXor = false); + static void decodeFrameDeltaPage(uint8 *dst, const uint8 *src, const int pitch, bool noXor); + + static void convertAmigaGfx(uint8 *data, int w, int h, int depth = 5, bool wsa = false, int bytesPerPlane = -1); + static void convertAmigaMsc(uint8 *data); + + // RPG specific, this does not belong here + void crossFadeRegion(int x1, int y1, int x2, int y2, int w, int h, int srcPage, int dstPage); + + uint16 *get16bitPalette() { return _16bitPalette; } + void set16bitShadingLevel(int lvl) { _16bitShadingLevel = lvl; } + +protected: + void resetPagePtrsAndBuffers(int pageSize); + uint8 *getPagePtr(int pageNum); + virtual void updateDirtyRects(); + void updateDirtyRectsAmiga(); + void updateDirtyRectsOvl(); + + void scale2x(byte *dst, int dstPitch, const byte *src, int srcPitch, int w, int h); + virtual void mergeOverlay(int x, int y, int w, int h); + + // overlay specific + byte *getOverlayPtr(int pageNum); + void clearOverlayPage(int pageNum); + void clearOverlayRect(int pageNum, int x, int y, int w, int h); + void copyOverlayRegion(int x, int y, int x2, int y2, int w, int h, int srcPage, int dstPage); + + // font/text specific + uint16 fetchChar(const char *&s) const; + void drawChar(uint16 c, int x, int y); + + int16 encodeShapeAndCalculateSize(uint8 *from, uint8 *to, int size); + + template static void wrapped_decodeFrameDelta(uint8 *dst, const uint8 *src); + template static void wrapped_decodeFrameDeltaPage(uint8 *dst, const uint8 *src, const int pitch); + + uint8 *_pagePtrs[16]; + uint8 *_sjisOverlayPtrs[SCREEN_OVLS_NUM]; + uint8 _pageMapping[SCREEN_PAGE_NUM]; + + bool _useOverlays; + bool _useSJIS; + bool _use16ColorMode; + bool _useHiResEGADithering; + bool _useHiColorScreen; + bool _isAmiga; + Common::RenderMode _renderMode; + int _bytesPerPixel; + int _screenPageSize; + + uint8 _sjisInvisibleColor; + bool _sjisMixedFontMode; + + Palette *_screenPalette; + Common::Array _palettes; + Palette *_internFadePalette; + + uint16 shade16bitColor(uint16 col); + + uint16 *_16bitPalette; + uint16 *_16bitConversionPalette; + uint8 _16bitShadingLevel; + + Font *_fonts[FID_NUM]; + uint8 _textColorsMap[16]; + uint16 _textColorsMap16bit[2]; + + uint8 *_decodeShapeBuffer; + int _decodeShapeBufferSize; + + uint8 *_animBlockPtr; + int _animBlockSize; + + // dimension handling + const ScreenDim *const _dimTable; + ScreenDim **_customDimTable; + const int _dimTableCount; + int _curDimIndex; + + // mouse handling + int _mouseLockCount; + const uint8 _cursorColorKey; + + virtual void postProcessCursor(uint8 *data, int w, int h, int pitch) {} + + enum { + kMaxDirtyRects = 50 + }; + + bool _forceFullUpdate; + bool _paletteChanged; + Common::List _dirtyRects; + + void addDirtyRect(int x, int y, int w, int h); + + OSystem *_system; + KyraEngine_v1 *_vm; + + // shape + int drawShapeMarginNoScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt); + int drawShapeMarginNoScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt); + int drawShapeMarginScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt); + int drawShapeMarginScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt); + int drawShapeSkipScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt); + int drawShapeSkipScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt); + void drawShapeProcessLineNoScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState); + void drawShapeProcessLineNoScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState); + void drawShapeProcessLineScaleUpwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState); + void drawShapeProcessLineScaleDownwind(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState); + + void drawShapePlotType0(uint8 *dst, uint8 cmd); + void drawShapePlotType1(uint8 *dst, uint8 cmd); + void drawShapePlotType3_7(uint8 *dst, uint8 cmd); + void drawShapePlotType4(uint8 *dst, uint8 cmd); + void drawShapePlotType5(uint8 *dst, uint8 cmd); + void drawShapePlotType6(uint8 *dst, uint8 cmd); + void drawShapePlotType8(uint8 *dst, uint8 cmd); + void drawShapePlotType9(uint8 *dst, uint8 cmd); + void drawShapePlotType11_15(uint8 *dst, uint8 cmd); + void drawShapePlotType12(uint8 *dst, uint8 cmd); + void drawShapePlotType13(uint8 *dst, uint8 cmd); + void drawShapePlotType14(uint8 *dst, uint8 cmd); + void drawShapePlotType16(uint8 *dst, uint8 cmd); + void drawShapePlotType20(uint8 *dst, uint8 cmd); + void drawShapePlotType21(uint8 *dst, uint8 cmd); + void drawShapePlotType33(uint8 *dst, uint8 cmd); + void drawShapePlotType37(uint8 *dst, uint8 cmd); + void drawShapePlotType48(uint8 *dst, uint8 cmd); + void drawShapePlotType52(uint8 *dst, uint8 cmd); + + typedef int (Screen::*DsMarginSkipFunc)(uint8 *&dst, const uint8 *&src, int &cnt); + typedef void (Screen::*DsLineFunc)(uint8 *&dst, const uint8 *&src, int &cnt, int16 scaleState); + typedef void (Screen::*DsPlotFunc)(uint8 *dst, uint8 cmd); + + DsMarginSkipFunc _dsProcessMargin; + DsMarginSkipFunc _dsScaleSkip; + DsLineFunc _dsProcessLine; + DsPlotFunc _dsPlot; + + const uint8 *_dsShapeFadingTable; + int _dsShapeFadingLevel; + const uint8 *_dsColorTable; + const uint8 *_dsTransparencyTable1; + const uint8 *_dsTransparencyTable2; + const uint8 *_dsBackgroundFadingTable; + int _dsDrawLayer; + uint8 *_dsDstPage; + int _dsTmpWidth; + int _dsOffscreenLeft; + int _dsOffscreenRight; + int _dsScaleW; + int _dsScaleH; + int _dsOffscreenScaleVal1; + int _dsOffscreenScaleVal2; + int _drawShapeVar1; + int _drawShapeVar3; + int _drawShapeVar4; + int _drawShapeVar5; + + // AMIGA version + bool _interfacePaletteEnabled; + + // debug + bool _debugEnabled; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/screen_eob.cpp b/engines/kyra/graphics/screen_eob.cpp new file mode 100644 index 0000000000..3945c034fc --- /dev/null +++ b/engines/kyra/graphics/screen_eob.cpp @@ -0,0 +1,1974 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#if !defined(ENABLE_EOB) +#include "kyra/graphics/screen.h" +#endif + +#ifdef ENABLE_EOB + +#include "kyra/engine/eobcommon.h" +#include "kyra/resource/resource.h" + +#include "common/system.h" + +#include "graphics/cursorman.h" +#include "graphics/palette.h" +#include "graphics/sjis.h" + +namespace Kyra { + +Screen_EoB::Screen_EoB(EoBCoreEngine *vm, OSystem *system) : Screen(vm, system, _screenDimTable, _screenDimTableCount), _cursorColorKey16Bit(0x8000) { + _dsBackgroundFading = false; + _dsShapeFadingLevel = 0; + _dsBackgroundFadingXOffs = 0; + _dsShapeFadingTable = 0; + _dsX1 = _dsX2 = _dsY1 = _dsY2 = 0; + _gfxX = _gfxY = 0; + _gfxCol = 0; + _dsTempPage = 0; + _shpBuffer = _convertHiColorBuffer = 0; + _dsDiv = 0; + _dsRem = 0; + _dsScaleTrans = 0; + _cgaScaleTable = 0; + _gfxMaxY = 0; + _egaDitheringTable = 0; + _egaDitheringTempPage = 0; + _cgaMappingDefault = 0; + _cgaDitheringTables[0] = _cgaDitheringTables[1] = 0; + _useHiResEGADithering = false; +} + +Screen_EoB::~Screen_EoB() { + delete[] _dsTempPage; + delete[] _shpBuffer; + delete[] _convertHiColorBuffer; + delete[] _cgaScaleTable; + delete[] _egaDitheringTable; + delete[] _egaDitheringTempPage; + delete[] _cgaDitheringTables[0]; + delete[] _cgaDitheringTables[1]; +} + +bool Screen_EoB::init() { + if (Screen::init()) { + int temp; + _gfxMaxY = _vm->staticres()->loadRawData(kEoBBaseExpObjectY, temp); + _dsTempPage = new uint8[12000]; + + if (_vm->gameFlags().platform == Common::kPlatformFMTowns) { + _shpBuffer = new uint8[SCREEN_H * SCREEN_W]; + _convertHiColorBuffer = new uint8[SCREEN_H * SCREEN_W]; + enableHiColorMode(true); + + Graphics::FontSJIS *font = Graphics::FontSJIS::createFont(Common::kPlatformFMTowns); + if (!font) + error("Could not load any SJIS font, neither the original nor ScummVM's 'SJIS.FNT'"); + _fonts[FID_SJIS_LARGE_FNT] = new SJISFontLarge(font); + + loadFont(FID_SJIS_SMALL_FNT, "FONT.DMP"); + } + + if (_vm->gameFlags().useHiRes && _renderMode == Common::kRenderEGA) { + _useHiResEGADithering = true; + _egaDitheringTable = new uint8[256]; + _egaDitheringTempPage = new uint8[SCREEN_W * 2 * SCREEN_H * 2]; + for (int i = 0; i < 256; i++) + _egaDitheringTable[i] = i & 0x0F; + } else if (_renderMode == Common::kRenderCGA) { + _cgaMappingDefault = _vm->staticres()->loadRawData(kEoB1CgaMappingDefault, temp); + _cgaDitheringTables[0] = new uint16[256]; + memset(_cgaDitheringTables[0], 0, 256 * sizeof(uint16)); + _cgaDitheringTables[1] = new uint16[256]; + memset(_cgaDitheringTables[1], 0, 256 * sizeof(uint16)); + + _cgaScaleTable = new uint8[256]; + memset(_cgaScaleTable, 0, 256 * sizeof(uint8)); + for (int i = 0; i < 256; i++) + _cgaScaleTable[i] = ((i & 0xF0) >> 2) | (i & 0x03); + } + + return true; + } + return false; +} + +void Screen_EoB::setClearScreenDim(int dim) { + setScreenDim(dim); + clearCurDim(); +} + +void Screen_EoB::clearCurDim() { + fillRect(_curDim->sx << 3, _curDim->sy, ((_curDim->sx + _curDim->w) << 3) - 1, (_curDim->sy + _curDim->h) - 1, _curDim->unkA); +} + +void Screen_EoB::setMouseCursor(int x, int y, const byte *shape) { + setMouseCursor(x, y, shape, 0); +} + +void Screen_EoB::setMouseCursor(int x, int y, const byte *shape, const uint8 *ovl) { + if (!shape) + return; + + int mouseW = (shape[2] << 3); + int mouseH = (shape[3]); + int colorKey = (_renderMode == Common::kRenderCGA) ? 0 : (_bytesPerPixel == 2 ? _cursorColorKey16Bit : _cursorColorKey); + + int scaleFactor = _vm->gameFlags().useHiRes ? 2 : 1; + int bpp = _useHiColorScreen ? 2 : 1; + + uint8 *cursor = new uint8[mouseW * scaleFactor * bpp * mouseH * scaleFactor]; + + if (_bytesPerPixel == 2) { + for (int s = mouseW * scaleFactor * bpp * mouseH * scaleFactor; s; s -= 2) + *(uint16*)(cursor + s - 2) = colorKey; + } else { + // We don't use fillRect here to make sure that the color key 0xFF doesn't get converted into EGA color + memset(cursor, colorKey, mouseW * scaleFactor * bpp * mouseH * scaleFactor); + } + + copyBlockToPage(6, 0, 0, mouseW * scaleFactor, mouseH * scaleFactor, cursor); + drawShape(6, shape, 0, 0, 0, 2, ovl); + CursorMan.showMouse(false); + + if (_useHiResEGADithering) + ditherRect(getCPagePtr(6), cursor, mouseW * scaleFactor, mouseW, mouseH, colorKey); + else if (_vm->gameFlags().useHiRes) + scale2x(cursor, mouseW * scaleFactor, getCPagePtr(6), SCREEN_W, mouseW, mouseH); + else + copyRegionToBuffer(6, 0, 0, mouseW, mouseH, cursor); + + // Mouse cursor post processing for CGA mode. Unlike the original (which uses drawShape for the mouse cursor) + // the cursor manager cannot know whether a pixel value of 0 is supposed to be black or transparent. Thus, we + // go over the transparency mask again and turn the black pixels to color 4. + if (_renderMode == Common::kRenderCGA) { + const uint8 *maskTbl = shape + 4 + ((mouseW * mouseH) >> 2); + uint8 *dst = cursor; + uint8 trans = 0; + uint8 shift = 6; + + uint16 mH = mouseH; + while (mH--) { + uint16 mW = mouseW; + while (mW--) { + if (shift == 6) + trans = *maskTbl++; + if (!*dst && !((trans >> shift) & 3)) + *dst = 4; + dst++; + shift = (shift - 2) & 7; + } + } + } + + // Convert color key to 16 bit after drawing the mouse cursor. + // The cursor has been converted to 16 bit in scale2x(). + colorKey = _16bitConversionPalette ? _16bitConversionPalette[colorKey] : colorKey; + Graphics::PixelFormat pixelFormat = _system->getScreenFormat(); + + CursorMan.replaceCursor(cursor, mouseW * scaleFactor, mouseH * scaleFactor, x * scaleFactor, y * scaleFactor, colorKey, false, &pixelFormat); + if (isMouseVisible()) + CursorMan.showMouse(true); + delete[] cursor; + + // makes sure that the cursor is drawn + // we do not use Screen::updateScreen here + // so we can be sure that changes to page 0 + // are NOT updated on the real screen here + _system->updateScreen(); +} + +void Screen_EoB::loadFileDataToPage(Common::SeekableReadStream *s, int pageNum, uint32 size) { + s->read(_pagePtrs[pageNum], size); +} + +void Screen_EoB::printShadedText(const char *string, int x, int y, int col1, int col2) { + if (_vm->gameFlags().platform != Common::kPlatformFMTowns) { + printText(string, x - 1, y, 12, col2); + printText(string, x, y + 1, 12, 0); + printText(string, x - 1, y + 1, 12, 0); + } else if (col2) { + fillRect(x, y, x + getTextWidth(string) - 1, y + getFontHeight() - 1, col2); + } + printText(string, x, y, col1, 0); +} + +void Screen_EoB::loadShapeSetBitmap(const char *file, int tempPage, int destPage) { + loadEoBBitmap(file, _cgaMappingDefault, tempPage, destPage, -1); + _curPage = 2; +} + +void Screen_EoB::loadEoBBitmap(const char *file, const uint8 *cgaMapping, int tempPage, int destPage, int convertToPage) { + const char *filePattern = _vm->gameFlags().platform == Common::kPlatformFMTowns ? "%s.SHP" : ((_vm->game() == GI_EOB1 && (_renderMode == Common::kRenderEGA || _renderMode == Common::kRenderCGA)) ? "%s.EGA" : "%s.CPS"); + Common::String tmp = Common::String::format(filePattern, file); + Common::SeekableReadStream *s = _vm->resource()->createReadStream(tmp); + bool loadAlternative = false; + + if (_vm->gameFlags().platform == Common::kPlatformFMTowns) { + if (!s) + error("Screen_EoB::loadEoBBitmap(): Failed to load file '%s'", file); + s->read(_shpBuffer, s->size()); + decodeSHP(_shpBuffer, destPage); + } else if (s) { + // This additional check is necessary since some localized versions of EOB II seem to contain invalid (size zero) cps files + if (s->size()) + loadBitmap(tmp.c_str(), tempPage, destPage, 0); + else + loadAlternative = true; + } else { + loadAlternative = true; + } + + delete s; + + if (loadAlternative) { + if (_vm->game() == GI_EOB1) { + tmp.insertChar('1', tmp.size() - 4); + loadBitmap(tmp.c_str(), tempPage, destPage, 0); + } else { + tmp.setChar('X', 0); + s = _vm->resource()->createReadStream(tmp); + if (!s) + error("Screen_EoB::loadEoBBitmap(): Failed to load file '%s'", file); + s->seek(768); + loadFileDataToPage(s, destPage, 64000); + delete s; + } + } + + if (convertToPage == -1) + return; + + if (_16bitPalette) + convertToHiColor(destPage); + + if (convertToPage == 2 && _renderMode == Common::kRenderCGA) { + convertPage(destPage, 4, cgaMapping); + copyRegion(0, 0, 0, 0, 320, 200, 4, 2, Screen::CR_NO_P_CHECK); + } else if (convertToPage == 0) { + convertPage(destPage, 2, cgaMapping); + copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK); + } else { + convertPage(destPage, convertToPage, cgaMapping); + } +} + +void Screen_EoB::convertPage(int srcPage, int dstPage, const uint8 *cgaMapping) { + uint8 *src = getPagePtr(srcPage); + uint8 *dst = getPagePtr(dstPage); + if (src == dst) + return; + + if (_renderMode == Common::kRenderCGA) { + if (cgaMapping) + generateCGADitheringTables(cgaMapping); + + uint16 *d = (uint16 *)dst; + uint8 tblSwitch = 0; + for (int height = SCREEN_H; height; height--) { + const uint16 *table = _cgaDitheringTables[(tblSwitch++) & 1]; + for (int width = SCREEN_W / 2; width; width--) { + WRITE_LE_UINT16(d++, table[((src[1] & 0x0F) << 4) | (src[0] & 0x0F)]); + src += 2; + } + } + } else if (_renderMode == Common::kRenderEGA && !_useHiResEGADithering) { + uint32 len = SCREEN_W * SCREEN_H; + while (len--) + *dst++ = *src++ & 0x0F; + } else { + copyPage(srcPage, dstPage); + } + + if (dstPage == 0 || dstPage == 1) + _forceFullUpdate = true; +} + +void Screen_EoB::setScreenPalette(const Palette &pal) { + if (_bytesPerPixel == 2) { + for (int i = 0; i < 4; i++) + createFadeTable16bit((const uint16*)(pal.getData()), &_16bitPalette[i * 256], 0, i * 85); + }else if (_useHiResEGADithering && pal.getNumColors() != 16) { + generateEGADitheringTable(pal); + } else if (_renderMode == Common::kRenderEGA && pal.getNumColors() == 16) { + _screenPalette->copy(pal); + _system->getPaletteManager()->setPalette(_screenPalette->getData(), 0, _screenPalette->getNumColors()); + } else if (_renderMode != Common::kRenderCGA && _renderMode != Common::kRenderEGA) { + Screen::setScreenPalette(pal); + } +} + +void Screen_EoB::getRealPalette(int num, uint8 *dst) { + if (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderEGA) { + const uint8 *pal = _screenPalette->getData(); + for (int i = 0; i < 16; ++i) { + dst[0] = (pal[0] << 2) | (pal[0] & 3); + dst[1] = (pal[1] << 2) | (pal[1] & 3); + dst[2] = (pal[2] << 2) | (pal[2] & 3); + dst += 3; + pal += 3; + } + } else { + Screen::getRealPalette(num, dst); + } +} + +uint8 *Screen_EoB::encodeShape(uint16 x, uint16 y, uint16 w, uint16 h, bool encode8bit, const uint8 *cgaMapping) { + uint8 *shp = 0; + uint16 shapesize = 0; + + uint8 *srcLineStart = getPagePtr(_curPage | 1) + y * 320 + (x << 3); + uint8 *src = srcLineStart; + + if (_renderMode == Common::kRenderEGA && !_useHiResEGADithering) + encode8bit = false; + + if (_bytesPerPixel == 2 && encode8bit) { + shapesize = h * (w << 3) + 4; + shp = new uint8[shapesize]; + memset(shp, 0, shapesize); + uint8 *dst = shp; + + *dst++ = 0; + *dst++ = (h & 0xFF); + *dst++ = (w & 0xFF); + *dst++ = (h & 0xFF); + + w <<= 3; + + for (int i = 0; i < h; ++i) { + memcpy(dst, src, w); + srcLineStart += SCREEN_W; + src = srcLineStart; + dst += w; + } + } else if (_renderMode == Common::kRenderCGA) { + if (cgaMapping) + generateCGADitheringTables(cgaMapping); + shapesize = h * (w << 2) + 4; + shp = new uint8[shapesize]; + memset(shp, 0, shapesize); + uint8 *dst = shp; + + *dst++ = 4; + *dst++ = (h & 0xFF); + *dst++ = (w & 0xFF); + *dst++ = (h & 0xFF); + + uint8 *dst2 = dst + (h * (w << 1)); + + uint8 tblSwitch = 0; + uint16 h1 = h; + while (h1--) { + uint16 w1 = w << 1; + const uint16 *table = _cgaDitheringTables[(tblSwitch++) & 1]; + + while (w1--) { + uint16 p0 = table[((src[1] & 0x0F) << 4) | (src[0] & 0x0F)]; + uint16 p1 = table[((src[3] & 0x0F) << 4) | (src[2] & 0x0F)]; + + *dst++ = ((p0 & 0x0003) << 6) | ((p0 & 0x0300) >> 4) | ((p1 & 0x0003) << 2) | ((p1 & 0x0300) >> 8); + + uint8 msk = 0; + for (int i = 0; i < 4; i++) { + if (!src[3 - i]) + msk |= (3 << (i << 1)); + } + *dst2++ = msk; + src += 4; + } + srcLineStart += SCREEN_W; + src = srcLineStart; + } + + } else if (encode8bit) { + uint16 h1 = h; + while (h1--) { + uint8 *lineEnd = src + (w << 3); + do { + if (!*src++) { + shapesize++; + uint8 *startZeroPos = src; + while (src != lineEnd && *src == 0) + src++; + + uint16 numZero = src - startZeroPos + 1; + if (numZero >> 8) + shapesize += 2; + } + shapesize++; + } while (src != lineEnd); + + srcLineStart += SCREEN_W; + src = srcLineStart; + } + + shapesize += 4; + + shp = new uint8[shapesize]; + memset(shp, 0, shapesize); + uint8 *dst = shp; + + *dst++ = 1; + *dst++ = (h & 0xFF); + *dst++ = (w & 0xFF); + *dst++ = (h & 0xFF); + + srcLineStart = getPagePtr(_curPage | 1) + y * 320 + (x << 3); + src = srcLineStart; + + h1 = h; + while (h1--) { + uint8 *lineEnd = src + (w << 3); + do { + uint8 val = *src++; + if (!val) { + *dst++ = 0; + uint8 *startZeroPos = src; + + while (src != lineEnd && *src == 0) + src++; + + uint16 numZero = src - startZeroPos + 1; + if (numZero >> 8) { + *dst++ = 255; + *dst++ = 0; + numZero -= 255; + } + val = numZero & 0xFF; + } + *dst++ = val; + } while (src != lineEnd); + + srcLineStart += SCREEN_W; + src = srcLineStart; + } + + } else { + uint8 nib = 0, col = 0; + uint8 *colorMap = 0; + + if (_renderMode != Common::kRenderEGA || _useHiResEGADithering) { + colorMap = new uint8[0x100]; + memset(colorMap, 0xFF, 0x100); + } + + shapesize = h * (w << 2) + 20; + shp = new uint8[shapesize]; + memset(shp, 0, shapesize); + uint8 *dst = shp; + + *dst++ = 2; + *dst++ = (h & 0xFF); + *dst++ = (w & 0xFF); + *dst++ = (h & 0xFF); + + if (_renderMode != Common::kRenderEGA || _useHiResEGADithering) { + memset(dst, 0xFF, 0x10); + } else { + for (int i = 0; i < 16; i++) + dst[i] = i; + } + + uint8 *pal = dst; + dst += 16; + nib = col = 0; + + uint16 h1 = h; + while (h1--) { + uint16 w1 = w << 3; + while (w1--) { + uint8 s = *src++; + uint8 c = s & 0x0F; + if (colorMap) { + c = colorMap[s]; + if (c == 0xFF) { + if (col < 0x10) { + *pal++ = s; + c = colorMap[s] = col++; + } else { + c = 0; + } + } + } + + if (++nib & 1) + *dst = c << 4; + else + *dst++ |= c; + } + srcLineStart += SCREEN_W; + src = srcLineStart; + } + delete[] colorMap; + } + + return shp; +} + +void Screen_EoB::drawShape(uint8 pageNum, const uint8 *shapeData, int x, int y, int sd, int flags, ...) { + uint8 *dst = getPagePtr(pageNum); + const uint8 *src = shapeData; + + if (!src) + return; + + if (sd != -1) { + const ScreenDim *dm = getScreenDim(sd); + setShapeFrame(dm->sx, dm->sy, dm->sx + dm->w, dm->sy + dm->h); + x += (_dsX1 << 3); + y += _dsY1; + } + + uint8 *ovl = 0; + + va_list args; + va_start(args, flags); + if (flags & 2) { + ovl = va_arg(args, uint8 *); + _dsBackgroundFadingXOffs = x; + } + va_end(args); + + dst += (_dsX1 << (2 + _bytesPerPixel)); + int16 dX = x - (_dsX1 << 3); + int16 dY = y; + int16 dW = _dsX2 - _dsX1; + + uint8 pixelsPerByte = *src++; + uint16 dH = *src++; + uint16 width = (*src++) << 3; + uint16 transOffset = (pixelsPerByte == 4) ? (dH * width) >> 2 : 0; + src++; + + int rX = x; + int rY = y; + int rW = width + 8; + int rH = dH; + + uint16 w2 = width; + int d = dY - _dsY1; + + int pixelStep = (flags & 1) ? -1 : 1; + + if (pixelsPerByte < 2) { + uint16 marginLeft = 0; + uint16 marginRight = 0; + + if (d < 0) { + dH += d; + if (dH <= 0) + return; + d = -d; + + for (int i = 0; i < d; i++) { + marginLeft = width; + for (int ii = 0; ii < marginLeft; ii++) { + if (!*src++) + marginLeft = marginLeft + 1 - *src++; + } + } + dY = _dsY1; + } + + d = _dsY2 - dY; + + if (d < 1) + return; + + if (d < dH) + dH = d; + + marginLeft = 0; + + if (dX < 0) { + width += dX; + marginLeft = -dX; + + if (marginLeft >= w2) + return; + + dX = 0; + } + + marginRight = 0; + d = (dW << 3) - dX; + + if (d < 1) + return; + + if (d < width) { + width = d; + marginRight = w2 - marginLeft - width; + } + + dst += (dY * SCREEN_W * _bytesPerPixel + dX * _bytesPerPixel); + uint8 *dstL = dst; + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(rX, rY, rW, rH); + + while (dH--) { + int16 xpos = (int16) marginLeft; + + if (flags & 1) { + if (pixelsPerByte == 1) { + for (int i = 0; i < w2; i++) { + if (*src++ == 0) { + i += (*src - 1); + src += (*src - 1); + } + } + } else { + src += w2; + } + src--; + } + const uint8 *src2 = src; + + if (xpos) { + if (pixelsPerByte == 1) { + do { + uint8 val = (flags & 1) ? *(src - 1) : *src; + while (val && xpos) { + src += pixelStep; + xpos--; + val = (flags & 1) ? *(src - 1) : *src; + } + + val = (flags & 1) ? *(src - 1) : *src; + if (!val) { + src += pixelStep; + uint8 bt = (flags & 1) ? src[1] : src[0]; + src += pixelStep; + xpos = xpos - bt; + } + } while (xpos > 0); + } else { + src += (xpos * pixelStep); + xpos = 0; + } + } + + dst -= xpos * _bytesPerPixel; + xpos += width; + + while (xpos > 0) { + uint8 c = *src; + uint8 m = (flags & 1) ? *(src - 1) : c; + src += pixelStep; + + if (m) { + drawShapeSetPixel(dst, c); + dst += _bytesPerPixel; + xpos--; + } else if (pixelsPerByte) { + uint8 len = (flags & 1) ? src[1] : src[0]; + dst += len * _bytesPerPixel; + xpos -= len; + src += pixelStep; + } else { + dst += _bytesPerPixel; + xpos--; + } + } + xpos += marginRight; + + if (xpos) { + do { + if (pixelsPerByte == 1) { + uint8 val = (flags & 1) ? *(src - 1) : *src; + while (val && xpos) { + src += pixelStep; + xpos--; + val = (flags & 1) ? *(src - 1) : *src; + } + + val = (flags & 1) ? *(src - 1) : *src; + if (!val) { + src += pixelStep; + uint8 bt = (flags & 1) ? src[1] : src[0]; + src += pixelStep; + xpos = xpos - bt; + } + } else { + src += (xpos * pixelStep); + xpos = 0; + } + } while (xpos > 0); + } + + dstL += SCREEN_W * _bytesPerPixel; + dst = dstL; + if (flags & 1) + src = src2 + 1; + } + } else { + const uint8 *pal = 0; + uint8 cgaPal[4]; + memset(cgaPal, 0, 4); + + if (pixelsPerByte == 2) { + pal = ovl ? ovl : src; + src += 16; + } else { + static const uint8 cgaDefOvl[] = { 0x00, 0x55, 0xAA, 0xFF }; + pal = ovl ? ovl : cgaDefOvl; + for (int i = 0; i < 4; i++) + cgaPal[i] = pal[i] & 3; + pal = cgaPal; + } + + if (d < 0) { + d = -d; + if (d >= dH) + return; + src += (d * (width / pixelsPerByte)); + d = dY + dH - _dsY1; + if (d >= 0) { + dH = d; + dY = _dsY1; + d = _dsY2 - dY; + } + } else { + d = _dsY2 - dY; + } + + if (d < 1) + return; + + if (d < dH) + dH = d; + + bool trimL = false; + uint8 dXbitAlign = dX & (pixelsPerByte - 1); + + if (dX < 0) { + width += dX; + d = -dX; + if (flags & 1) + src -= (d / pixelsPerByte); + else + src += (d / pixelsPerByte); + + if (d >= w2) + return; + + dX = 0; + trimL = true; + } + + d = (dW << 3) - dX; + + if (d < 1) + return; + + if (d < width) + width = d; + + dst += (dY * SCREEN_W * _bytesPerPixel + dX * _bytesPerPixel); + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(rX, rY, rW, rH); + + int pitch = SCREEN_W - width; + int16 lineSrcStep = (w2 - width) / pixelsPerByte; + uint8 lineSrcStepRemainder = (w2 - width) % pixelsPerByte; + + w2 /= pixelsPerByte; + if (flags & 1) + src += (w2 - 1); + + uint8 pixelPacking = 8 / pixelsPerByte; + uint8 pixelPackingMask = 0; + + for (int i = 0; i < pixelPacking; i++) + pixelPackingMask |= (1 << i); + + if (trimL && (dXbitAlign > lineSrcStepRemainder)) + lineSrcStep--; + + uint8 bitShDef = 8 - pixelPacking; + if (flags & 1) { + lineSrcStep = (w2 << 1) - lineSrcStep; + bitShDef = 0; + } + + uint8 bitShLineStart = bitShDef; + if (trimL) + bitShLineStart -= (dXbitAlign * pixelStep * pixelPacking); + + while (dH--) { + int16 wd = width; + uint8 in = 0; + uint8 trans = 0; + uint8 shift = bitShLineStart; + uint8 shSwtch = bitShLineStart; + + while (wd--) { + if (shift == shSwtch) { + in = *src; + trans = src[transOffset]; + src += pixelStep; + shSwtch = bitShDef; + } + uint8 col = (pixelsPerByte == 2) ? pal[(in >> shift) & pixelPackingMask] : (*dst & ((trans >> shift) & (pixelPackingMask))) | pal[(in >> shift) & pixelPackingMask]; + if (col || pixelsPerByte == 4) + drawShapeSetPixel(dst, col); + dst += _bytesPerPixel; + shift = ((shift - (pixelStep * pixelPacking)) & 7); + } + src += lineSrcStep; + dst += (pitch * _bytesPerPixel); + } + } +} + +const uint8 *Screen_EoB::scaleShape(const uint8 *shapeData, int steps) { + setShapeFadingLevel(steps); + + while (shapeData && steps--) + shapeData = scaleShapeStep(shapeData); + + return shapeData; +} + +const uint8 *Screen_EoB::scaleShapeStep(const uint8 *shp) { + uint8 *dst = (shp != _dsTempPage) ? _dsTempPage : _dsTempPage + 6000; + uint8 *d = dst; + uint8 pixelsPerByte = *d++ = *shp++; + assert(pixelsPerByte > 1); + + uint16 h = shp[0] + 1; + d[0] = d[2] = (h << 1) / 3; + + uint16 w = shp[1]; + uint16 w2 = (w << 3) / pixelsPerByte; + uint16 t = ((w << 1) % 3) ? 1 : 0; + d[1] = ((w << 1) / 3) + t; + + uint32 transOffsetSrc = (pixelsPerByte == 4) ? (shp[0] * shp[1]) << 1 : 0; + uint32 transOffsetDst = (pixelsPerByte == 4) ? (d[0] * d[1]) << 1 : 0; + shp += 3; + d += 3; + + if (pixelsPerByte == 2) { + int i = 0; + while (i < 16) { + if (!shp[i]) { + i = -i; + break; + } + i++; + } + + if (i >= 0) + i = 0; + else + i = -i; + + _dsScaleTrans = (i << 4) | (i & 0x0F); + for (int ii = 0; ii < 16; ii++) + *d++ = *shp++; + } + + _dsDiv = w2 / 3; + _dsRem = w2 % 3; + + while (--h) { + if (pixelsPerByte == 2) + scaleShapeProcessLine4Bit(d, shp); + else + scaleShapeProcessLine2Bit(d, shp, transOffsetDst, transOffsetSrc); + if (!--h) + break; + if (pixelsPerByte == 2) + scaleShapeProcessLine4Bit(d, shp); + else + scaleShapeProcessLine2Bit(d, shp, transOffsetDst, transOffsetSrc); + if (!--h) + break; + shp += w2; + } + + return (const uint8 *)dst; +} + +const uint8 *Screen_EoB::generateShapeOverlay(const uint8 *shp, const uint8 *fadingTable) { + if (*shp != 2) + return 0; + + if (_bytesPerPixel == 2) { + setFadeTable(fadingTable); + setShapeFadingLevel(1); + return 0; + } + + shp += 4; + for (int i = 0; i < 16; i++) + _shapeOverlay[i] = fadingTable[shp[i]]; + return _shapeOverlay; +} + +void Screen_EoB::setShapeFrame(int x1, int y1, int x2, int y2) { + _dsX1 = x1; + _dsY1 = y1; + _dsX2 = x2; + _dsY2 = y2; +} + +void Screen_EoB::enableShapeBackgroundFading(bool enable) { + _dsBackgroundFading = enable; +} + +void Screen_EoB::setShapeFadingLevel(int level) { + _dsShapeFadingLevel = level; +} + +void Screen_EoB::setGfxParameters(int x, int y, int col) { + _gfxX = x; + _gfxY = y; + _gfxCol = col; +} + +void Screen_EoB::drawExplosion(int scale, int radius, int numElements, int stepSize, int aspectRatio, const uint8 *colorTable, int colorTableSize) { + int ymin = 0; + int ymax = _gfxMaxY[scale]; + int xmin = -100; + int xmax = 276; + + if (scale) + --scale; + + hideMouse(); + + const ScreenDim *dm = getScreenDim(5); + int rX1 = dm->sx << 3; + int rY1 = dm->sy; + int rX2 = rX1 + (dm->w << 3); + int rY2 = rY1 + dm->h - 1; + + int16 gx2 = _gfxX; + int16 gy2 = _gfxY; + + int16 *ptr2 = (int16 *)_dsTempPage; + int16 *ptr3 = (int16 *)&_dsTempPage[300]; + int16 *ptr4 = (int16 *)&_dsTempPage[600]; + int16 *ptr5 = (int16 *)&_dsTempPage[900]; + int16 *ptr6 = (int16 *)&_dsTempPage[1200]; + int16 *ptr7 = (int16 *)&_dsTempPage[1500]; + int16 *ptr8 = (int16 *)&_dsTempPage[1800]; + + if (numElements > 150) + numElements = 150; + + for (int i = 0; i < numElements; i++) { + ptr2[i] = ptr3[i] = 0; + ptr4[i] = _vm->_rnd.getRandomNumberRng(0, radius) - (radius >> 1); + ptr5[i] = _vm->_rnd.getRandomNumberRng(0, radius) - (radius >> 1) - (radius >> (8 - aspectRatio)); + ptr7[i] = _vm->_rnd.getRandomNumberRng(1024 / stepSize, 2048 / stepSize); + ptr8[i] = scale << 8; + } + + for (int l = 2; l;) { + if (l != 2) { + for (int i = numElements - 1; i >= 0; i--) { + int16 px = ((ptr2[i] >> 6) >> scale) + gx2; + int16 py = ((ptr3[i] >> 6) >> scale) + gy2; + if (py > ymax) + py = ymax; + if (posWithinRect(px, py, rX1, rY1, rX2, rY2)) { + if (_bytesPerPixel == 2) + setPagePixel16bit(0, px, py, ptr6[i]); + else + setPagePixel(0, px, py, ptr6[i]); + } + } + } + + l = 0; + + for (int i = 0; i < numElements; i++) { + uint32 end = _system->getMillis() + 1; + if (ptr4[i] <= 0) + ptr4[i]++; + else + ptr4[i]--; + ptr2[i] += ptr4[i]; + ptr5[i] += 5; + ptr3[i] += ptr5[i]; + ptr8[i] += ptr7[i]; + + int16 px = ((ptr2[i] >> 6) >> scale) + gx2; + int16 py = ((ptr3[i] >> 6) >> scale) + gy2; + if (py >= ymax || py < ymin) + ptr5[i] = -(ptr5[i] >> 1); + if (px >= xmax || px < xmin) + ptr4[i] = -(ptr4[i] >> 1); + + if (py > ymax) + py = ymax; + + int pxVal1 = 0; + if (posWithinRect(px, py, 0, 0, 319, 199)) { + pxVal1 = getPagePixel(2, px, py); + ptr6[i] = getPagePixel(0, px, py); + } + + assert((ptr8[i] >> 8) < colorTableSize); + int pxVal2 = colorTable[ptr8[i] >> 8]; + if (pxVal2) { + l = 1; + if (pxVal1 == _gfxCol && posWithinRect(px, py, rX1, rY1, rX2, rY2)) { + setPagePixel(0, px, py, pxVal2); + if (i % 5 == 0) { + updateScreen(); + uint32 cur = _system->getMillis(); + if (end > cur) + _system->delayMillis(end - cur); + } + } + } else { + ptr7[i] = 0; + } + } + } + + showMouse(); +} + +void Screen_EoB::drawVortex(int numElements, int radius, int stepSize, int, int disorder, const uint8 *colorTable, int colorTableSize) { + int16 *xCoords = (int16 *)_dsTempPage; + int16 *yCoords = (int16 *)&_dsTempPage[300]; + int16 *xMod = (int16 *)&_dsTempPage[600]; + int16 *yMod = (int16 *)&_dsTempPage[900]; + int16 *pixBackup = (int16 *)&_dsTempPage[1200]; + int16 *colTableStep = (int16 *)&_dsTempPage[1500]; + int16 *colTableIndex = (int16 *)&_dsTempPage[1800]; + int16 *pixDelay = (int16 *)&_dsTempPage[2100]; + + hideMouse(); + int cp = _curPage; + + if (numElements > 150) + numElements = 150; + + int cx = 88; + int cy = 48; + radius <<= 6; + + for (int i = 0; i < numElements; i++) { + int16 v38 = _vm->_rnd.getRandomNumberRng(radius >> 2, radius); + int16 stepSum = 0; + int16 sqsum = 0; + while (sqsum < v38) { + stepSum += stepSize; + sqsum += stepSum; + } + + switch (_vm->_rnd.getRandomNumber(255) & 3) { + case 0: + xCoords[i] = 32; + yCoords[i] = sqsum; + xMod[i] = stepSum; + yMod[i] = 0; + break; + + case 1: + xCoords[i] = sqsum; + yCoords[i] = 32; + xMod[i] = 0; + yMod[i] = stepSum; + break; + + case 2: + xCoords[i] = 32; + yCoords[i] = -sqsum; + xMod[i] = stepSum; + yMod[i] = 0; + break; + + default: + xCoords[i] = -sqsum; + yCoords[i] = 32; + xMod[i] = 0; + yMod[i] = stepSum; + break; + } + + if (_vm->_rnd.getRandomBit()) { + xMod[i] *= -1; + yMod[i] *= -1; + } + + colTableStep[i] = _vm->_rnd.getRandomNumberRng(1024 / disorder, 2048 / disorder); + colTableIndex[i] = 0; + pixDelay[i] = _vm->_rnd.getRandomNumberRng(0, disorder >> 2); + } + + int d = 0; + for (int i = 2; i;) { + if (i != 2) { + for (int ii = numElements - 1; ii >= 0; ii--) { + int16 px = CLIP((xCoords[ii] >> 6) + cx, 0, SCREEN_W - 1); + int16 py = CLIP((yCoords[ii] >> 6) + cy, 0, SCREEN_H - 1); + if (_bytesPerPixel == 2) + setPagePixel16bit(0, px, py, pixBackup[ii]); + else + setPagePixel(0, px, py, pixBackup[ii]); + } + } + + i = 0; + int r = (stepSize >> 1) + (stepSize >> 2) + (stepSize >> 3); + uint32 nextDelay = _system->getMillis() + 1; + + for (int ii = 0; ii < numElements; ii++) { + if (pixDelay[ii] == 0) { + if (xCoords[ii] > 0) { + xMod[ii] -= ((xMod[ii] > 0) ? stepSize : r); + } else { + xMod[ii] += ((xMod[ii] < 0) ? stepSize : r); + } + + if (yCoords[ii] > 0) { + yMod[ii] -= ((yMod[ii] > 0) ? stepSize : r); + } else { + yMod[ii] += ((yMod[ii] < 0) ? stepSize : r); + } + + xCoords[ii] += xMod[ii]; + yCoords[ii] += yMod[ii]; + colTableIndex[ii] += colTableStep[ii]; + + } else { + pixDelay[ii]--; + } + + int16 px = CLIP((xCoords[ii] >> 6) + cx, 0, SCREEN_W - 1); + int16 py = CLIP((yCoords[ii] >> 6) + cy, 0, SCREEN_H - 1); + + uint8 tc1 = ((disorder >> 2) <= d) ? getPagePixel(2, px, py) : 0; + pixBackup[ii] = getPagePixel(0, px, py); + uint8 tblIndex = CLIP(colTableIndex[ii] >> 8, 0, colorTableSize - 1); + uint8 tc2 = colorTable[tblIndex]; + + if (tc2) { + i = 1; + if (tc1 == _gfxCol && !pixDelay[ii]) { + setPagePixel(0, px, py, tc2); + if (ii % 15 == 0) { + updateScreen(); + uint32 cur = _system->getMillis(); + if (nextDelay > cur) + _system->delayMillis(nextDelay - cur); + nextDelay += 1; + } + } + } else { + colTableStep[ii] = 0; + } + } + d++; + } + + _curPage = cp; + showMouse(); +} + +void Screen_EoB::fadeTextColor(Palette *pal, int color1, int rate) { + uint8 *col = pal->getData(); + + for (bool loop = true; loop;) { + loop = true; + uint32 end = _system->getMillis() + _vm->tickLength(); + + loop = false; + for (int ii = 0; ii < 3; ii++) { + uint8 c = col[color1 * 3 + ii]; + if (c > rate) { + col[color1 * 3 + ii] -= rate; + loop = true; + } else if (c) { + col[color1 * 3 + ii] = 0; + loop = true; + } + } + + if (loop) { + setScreenPalette(*pal); + updateScreen(); + uint32 cur = _system->getMillis(); + if (end > cur) + _system->delayMillis(end - cur); + } + } +} + +bool Screen_EoB::delayedFadePalStep(Palette *fadePal, Palette *destPal, int rate) { + bool res = false; + + uint8 *s = fadePal->getData(); + uint8 *d = destPal->getData(); + + for (int i = 0; i < 765; i++) { + int fadeVal = *s++; + int dstCur = *d; + int diff = ABS(fadeVal - dstCur); + + if (diff == 0) { + d++; + continue; + } + + res = true; + diff = MIN(diff, rate); + + if (dstCur < fadeVal) + *d += diff; + else + *d -= diff; + d++; + } + + return res; +} + +int Screen_EoB::getRectSize(int w, int h) { + return w * h; +} + +void Screen_EoB::setFadeTable(const uint8 *table) { + _dsShapeFadingTable = table; + if (_bytesPerPixel == 2) + memcpy(&_16bitPalette[0x100], table, 512); +} + +void Screen_EoB::createFadeTable(const uint8 *palData, uint8 *dst, uint8 rootColor, uint8 weight) { + if (!palData) + return; + + const uint8 *src = palData + 3 * rootColor; + uint8 r = *src++; + uint8 g = *src++; + uint8 b = *src; + uint8 tr, tg, tb; + src = palData + 3; + + *dst++ = 0; + weight >>= 1; + + for (uint8 i = 1; i; i++) { + uint16 tmp = (uint16)((*src - r) * weight) << 1; + tr = *src++ - ((tmp >> 8) & 0xFF); + tmp = (uint16)((*src - g) * weight) << 1; + tg = *src++ - ((tmp >> 8) & 0xFF); + tmp = (uint16)((*src - b) * weight) << 1; + tb = *src++ - ((tmp >> 8) & 0xFF); + + const uint8 *d = palData + 3; + uint16 v = 0xFFFF; + uint8 col = rootColor; + + for (uint8 ii = 1; ii; ii++) { + int a = *d++ - tr; + int t = a * a; + a = *d++ - tg; + t += (a * a); + a = *d++ - tb; + t += (a * a); + + if (t <= v && (ii == rootColor || ii != i)) { + v = t; + col = ii; + } + } + *dst++ = col; + } +} + +void Screen_EoB::createFadeTable16bit(const uint16 *palData, uint16 *dst, uint16 rootColor, uint8 weight) { + rootColor = palData[rootColor]; + uint8 r8 = (rootColor & 0x1f); + uint8 g8 = (rootColor & 0x3E0) >> 5; + uint8 b8 = (rootColor & 0x7C00) >> 10; + + int root_r = r8 << 4; + int root_g = g8 << 4; + int root_b = b8 << 4; + + *dst++ = palData[0]; + + for (uint8 i = 1; i; i++) { + r8 = (palData[i] & 0x1f); + g8 = (palData[i] & 0x3E0) >> 5; + b8 = (palData[i] & 0x7C00) >> 10; + + int red = r8 << 4; + int green = g8 << 4; + int blue = b8 << 4; + + if (red > root_r) { + red -= weight; + if (root_r > red) + red = root_r; + } else { + red += weight; + if (root_r < red) + red = root_r; + } + + if (green > root_g) { + green -= weight; + if (root_g > green) + green = root_g; + } else { + green += weight; + if (root_g < green) + green = root_g; + } + + if (blue > root_b) { + blue -= weight; + if (root_b > blue) + blue = root_b; + } else { + blue += weight; + if (root_b < blue) + blue = root_b; + } + + r8 = red >> 4; + g8 = green >> 4; + b8 = blue >> 4; + + *dst++ = (b8 << 10) | (g8 << 5) | r8; + } +} + +const uint16 *Screen_EoB::getCGADitheringTable(int index) { + return !(index & ~1) ? _cgaDitheringTables[index] : 0; +} + +const uint8 *Screen_EoB::getEGADitheringTable() { + return _egaDitheringTable; +} + +bool Screen_EoB::loadFont(FontId fontId, const char *filename) { + if (scumm_stricmp(filename, "FONT.DMP")) + return Screen::loadFont(fontId, filename); + + Font *&fnt = _fonts[fontId]; + int temp; + + const uint16 *tbl = _vm->staticres()->loadRawDataBe16(kEoB2FontDmpSearchTbl, temp); + assert(tbl); + + if (!fnt) { + fnt = new SJISFont12x12(tbl); + assert(fnt); + } + + Common::SeekableReadStream *file = _vm->resource()->createReadStream(filename); + if (!file) + error("Font file '%s' is missing", filename); + + bool ret = fnt->load(*file); + fnt->setColorMap(_textColorsMap); + delete file; + return ret; +} + +void Screen_EoB::decodeSHP(const uint8 *data, int dstPage) { + int32 bytesLeft = READ_LE_UINT32(data); + const uint8 *src = data + 4; + uint8 *dst = getPagePtr(dstPage); + + if (bytesLeft < 0) { + memcpy(dst, data, 64000); + return; + } + + while (bytesLeft > 0) { + uint8 code = *src++; + bytesLeft--; + + for (int i = 8; i; i--) { + if (code & 0x80) { + uint16 copyOffs = (src[0] << 4) | (src[1] >> 4); + uint8 count = (src[1] & 0x0F) + 3; + src += 2; + bytesLeft -= 2; + const uint8 *copySrc = dst - 1 - copyOffs; + while (count--) + *dst++ = *copySrc++; + } else if (bytesLeft) { + *dst++ = *src++; + bytesLeft--; + } else { + break; + } + code <<= 1; + } + } +} + +void Screen_EoB::convertToHiColor(int page) { + if (!_16bitPalette) + return; + uint16 *dst = (uint16 *)getPagePtr(page); + memcpy(_convertHiColorBuffer, dst, SCREEN_H * SCREEN_W); + uint8 *src = _convertHiColorBuffer; + for (int s = SCREEN_H * SCREEN_W; s; --s) + *dst++ = _16bitPalette[*src++]; +} + +void Screen_EoB::shadeRect(int x1, int y1, int x2, int y2, int shadingLevel) { + if (!_16bitPalette) + return; + + int l = _16bitShadingLevel; + _16bitShadingLevel = shadingLevel; + + if (_curPage == 0 || _curPage == 1) + addDirtyRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); + + uint16 *dst = (uint16*)(getPagePtr(_curPage) + y1 * SCREEN_W * _bytesPerPixel + x1 * _bytesPerPixel); + + for (; y1 < y2; ++y1) { + uint16 *ptr = dst; + for (int i = 0; i < x2 - x1; i++) { + *ptr = shade16bitColor(*ptr); + ptr++; + } + dst += SCREEN_W; + } + + _16bitShadingLevel = l; +} + +void Screen_EoB::updateDirtyRects() { + if (!_useHiResEGADithering) { + Screen::updateDirtyRects(); + return; + } + + if (_forceFullUpdate) { + ditherRect(getCPagePtr(0), _egaDitheringTempPage, SCREEN_W * 2, SCREEN_W, SCREEN_H); + _system->copyRectToScreen(_egaDitheringTempPage, SCREEN_W * 2, 0, 0, SCREEN_W * 2, SCREEN_H * 2); + } else { + const byte *page0 = getCPagePtr(0); + Common::List::iterator it; + for (it = _dirtyRects.begin(); it != _dirtyRects.end(); ++it) { + ditherRect(page0 + it->top * SCREEN_W + it->left, _egaDitheringTempPage, SCREEN_W * 2, it->width(), it->height()); + _system->copyRectToScreen(_egaDitheringTempPage, SCREEN_W * 2, it->left * 2, it->top * 2, it->width() * 2, it->height() * 2); + } + } + _forceFullUpdate = false; + _dirtyRects.clear(); +} + +void Screen_EoB::ditherRect(const uint8 *src, uint8 *dst, int dstPitch, int srcW, int srcH, int colorKey) { + while (srcH--) { + uint8 *dst2 = dst + dstPitch; + for (int i = 0; i < srcW; i++) { + int in = *src++; + if (in != colorKey) { + in = _egaDitheringTable[in]; + *dst++ = *dst2++ = in >> 4; + *dst++ = *dst2++ = in & 0x0F; + } else { + dst[0] = dst[1] = dst2[0] = dst2[1] = colorKey; + dst += 2; + dst2 += 2; + } + } + src += (SCREEN_W - srcW); + dst += ((dstPitch - srcW) * 2); + } +} + +void Screen_EoB::drawShapeSetPixel(uint8 *dst, uint8 col) { + if (_bytesPerPixel == 2) { + *(uint16*)dst = _16bitPalette[(_dsShapeFadingLevel << 8) + col]; + return; + } else if ((_renderMode != Common::kRenderCGA && _renderMode != Common::kRenderEGA) || _useHiResEGADithering) { + if (_dsBackgroundFading) { + if (_dsShapeFadingLevel) { + col = *dst; + } else { + _dsBackgroundFadingXOffs &= 7; + col = *(dst + _dsBackgroundFadingXOffs++); + } + } + + if (_dsShapeFadingLevel) { + uint8 cnt = _dsShapeFadingLevel; + while (cnt--) + col = _dsShapeFadingTable[col]; + } + } + + *dst = col; +} + +void Screen_EoB::scaleShapeProcessLine2Bit(uint8 *&shpDst, const uint8 *&shpSrc, uint32 transOffsetDst, uint32 transOffsetSrc) { + for (int i = 0; i < _dsDiv; i++) { + shpDst[0] = (_cgaScaleTable[shpSrc[0]] << 2) | (shpSrc[1] >> 6); + shpDst[1] = ((shpSrc[1] & 0x0F) << 4) | ((shpSrc[2] >> 2) & 0x0F); + shpDst[transOffsetDst] = (_cgaScaleTable[shpSrc[transOffsetSrc]] << 2) | (shpSrc[transOffsetSrc + 1] >> 6); + shpDst[transOffsetDst + 1] = ((shpSrc[transOffsetSrc + 1] & 0x0F) << 4) | ((shpSrc[transOffsetSrc + 2] >> 2) & 0x0F); + shpSrc += 3; + shpDst += 2; + } + + if (_dsRem == 1) { + shpDst[0] = _cgaScaleTable[shpSrc[0]] << 2; + shpDst[1] = 0; + shpDst[transOffsetDst] = (_cgaScaleTable[shpSrc[transOffsetSrc]] << 2) | 3; + shpDst[transOffsetDst + 1] = 0xFF; + shpSrc++; + shpDst += 2; + + } else if (_dsRem == 2) { + shpDst[0] = (_cgaScaleTable[shpSrc[0]] << 2) | (shpSrc[1] >> 6); + shpDst[1] = (shpSrc[1] & 0x3F) << 2; + shpDst[transOffsetDst] = (_cgaScaleTable[shpSrc[transOffsetSrc]] << 2) | (shpSrc[transOffsetSrc + 1] >> 6); + shpDst[transOffsetDst + 1] = ((shpSrc[transOffsetSrc + 1] & 0x3F) << 2) | 3; + shpSrc += 2; + shpDst += 2; + } +} + +void Screen_EoB::scaleShapeProcessLine4Bit(uint8 *&dst, const uint8 *&src) { + for (int i = 0; i < _dsDiv; i++) { + *dst++ = *src++; + *dst++ = (READ_BE_UINT16(src) >> 4) & 0xFF; + src += 2; + } + + if (_dsRem == 1) { + *dst++ = *src++; + *dst++ = _dsScaleTrans; + } else if (_dsRem == 2) { + *dst++ = (src[0] & 0xF0) | (src[1] >> 4); + src += 2; + *dst++ = _dsScaleTrans; + *dst++ = _dsScaleTrans; + *dst++ = _dsScaleTrans; + } +} + +bool Screen_EoB::posWithinRect(int posX, int posY, int x1, int y1, int x2, int y2) { + if (posX < x1 || posX > x2 || posY < y1 || posY > y2) + return false; + return true; +} + +void Screen_EoB::setPagePixel16bit(int pageNum, int x, int y, uint16 color) { + assert(pageNum < SCREEN_PAGE_NUM); + assert(x >= 0 && x < SCREEN_W && y >= 0 && y < SCREEN_H); + assert(_bytesPerPixel == 2); + + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, 1, 1); + + ((uint16*)_pagePtrs[pageNum])[y * SCREEN_W + x] = color; +} + +void Screen_EoB::generateEGADitheringTable(const Palette &pal) { + assert(_egaDitheringTable); + const uint8 *src = pal.getData(); + uint8 *dst = _egaDitheringTable; + + for (int i = 256; i; i--) { + int r = *src++; + int g = *src++; + int b = *src++; + + uint8 col = 0; + uint16 min = 0x2E83; + + for (int ii = 256; ii; ii--) { + const uint8 *palEntry = _egaMatchTable + (ii - 1) * 3; + if (*palEntry == 0xFF) + continue; + + int e_r = palEntry[0] - r; + int e_g = palEntry[1] - g; + int e_b = palEntry[2] - b; + + uint16 s = (e_r * e_r) + (e_g * e_g) + (e_b * e_b); + + if (s <= min) { + min = s; + col = ii - 1; + } + } + *dst++ = col; + } +} + +void Screen_EoB::generateCGADitheringTables(const uint8 *mappingData) { + for (int i = 0; i < 256; i++) { + _cgaDitheringTables[0][i] = (mappingData[(i >> 4) + 16] << 8) | mappingData[i & 0x0F]; + _cgaDitheringTables[1][i] = (mappingData[i >> 4] << 8) | mappingData[(i & 0x0F) + 16]; + } +} + +const uint8 Screen_EoB::_egaMatchTable[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x00, 0x1E, 0x1E, 0x1E, 0x00, 0x00, 0x1E, + 0x00, 0x1E, 0x1E, 0x0F, 0x00, 0x1E, 0x1E, 0x1E, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x2D, 0x0F, 0x2D, + 0x0F, 0x0F, 0x2D, 0x2D, 0x2D, 0x0F, 0x0F, 0x2D, 0x0F, 0x2D, 0x2D, 0x2D, 0x0F, 0x2D, 0x2D, 0x2D, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x2A, 0x00, 0x1E, 0x1E, 0x00, 0x1E, 0x2A, 0x1E, 0x00, 0x1E, 0x1E, + 0x00, 0x2A, 0x1E, 0x0F, 0x1E, 0x1E, 0x1E, 0x2A, 0x0F, 0x0F, 0x21, 0x0F, 0x0F, 0x36, 0x0F, 0x2D, + 0x21, 0x0F, 0x2D, 0x36, 0x2D, 0x0F, 0x21, 0x2D, 0x0F, 0x36, 0x2D, 0x2D, 0x21, 0x2D, 0x2D, 0x36, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x2A, 0x00, 0x00, 0x2A, 0x1E, 0x1E, 0x1E, 0x00, 0x1E, + 0x1E, 0x1E, 0x1E, 0x21, 0x00, 0x1E, 0x2A, 0x1E, 0x0F, 0x21, 0x0F, 0x0F, 0x21, 0x2D, 0x0F, 0x36, + 0x0F, 0x0F, 0x36, 0x2D, 0x2D, 0x21, 0x0F, 0x2D, 0x21, 0x2D, 0x2D, 0x36, 0x0F, 0x2D, 0x36, 0x2D, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x2A, 0x2A, 0x1E, 0x1E, 0x1E, 0x1E, + 0x1E, 0x2A, 0x1E, 0x21, 0x1E, 0x1E, 0x2A, 0x2A, 0x0F, 0x21, 0x21, 0x0F, 0x21, 0x36, 0x0F, 0x36, + 0x21, 0x0F, 0x36, 0x36, 0x2D, 0x21, 0x21, 0x2D, 0x21, 0x36, 0x2D, 0x36, 0x21, 0x2D, 0x36, 0x36, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2A, 0x00, 0x00, 0x2A, + 0x00, 0x1E, 0x2A, 0x0F, 0x00, 0x2A, 0x1E, 0x1E, 0x21, 0x0F, 0x0F, 0x21, 0x0F, 0x2D, 0x21, 0x2D, + 0x0F, 0x21, 0x2D, 0x2D, 0x36, 0x0F, 0x0F, 0x36, 0x0F, 0x2D, 0x36, 0x2D, 0x0F, 0x36, 0x2D, 0x2D, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2A, + 0x00, 0x2A, 0x2A, 0x0F, 0x1E, 0x2A, 0x1E, 0x2A, 0x21, 0x0F, 0x21, 0x21, 0x0F, 0x36, 0x21, 0x2D, + 0x21, 0x21, 0x2D, 0x36, 0x36, 0x0F, 0x21, 0x36, 0x0F, 0x36, 0x36, 0x2D, 0x21, 0x36, 0x2D, 0x36, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x2A, 0x15, 0x00, 0x2A, 0x21, 0x1E, 0x21, 0x15, 0x0F, 0x21, 0x15, 0x2D, 0x21, 0x2F, + 0x0F, 0x21, 0x2F, 0x2D, 0x36, 0x15, 0x0F, 0x36, 0x15, 0x2D, 0x36, 0x2F, 0x0F, 0x36, 0x2F, 0x2D, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2A, 0x2A, 0x2A, 0x21, 0x21, 0x21, 0x21, 0x21, 0x36, 0x21, 0x36, + 0x21, 0x21, 0x36, 0x36, 0x36, 0x21, 0x21, 0x36, 0x21, 0x36, 0x36, 0x36, 0x21, 0x36, 0x36, 0x36, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x15, 0x15, 0x15, 0x15, 0x15, 0x2F, 0x15, 0x2F, + 0x15, 0x15, 0x2F, 0x2F, 0x2F, 0x15, 0x15, 0x2F, 0x15, 0x2F, 0x2F, 0x2F, 0x15, 0x2F, 0x2F, 0x2F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x15, 0x15, 0x3F, 0x15, 0x2F, + 0x2F, 0x15, 0x2F, 0x3F, 0x2F, 0x15, 0x2F, 0x2F, 0x15, 0x3F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x15, 0x3F, + 0x15, 0x15, 0x3F, 0x2F, 0x2F, 0x2F, 0x15, 0x2F, 0x2F, 0x2F, 0x2F, 0x3F, 0x15, 0x2F, 0x3F, 0x2F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x15, 0x3F, 0x3F, 0x2F, 0x2F, 0x2F, 0x2F, 0x2F, 0x3F, 0x2F, 0x3F, 0x2F, 0x2F, 0x3F, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x15, 0x15, 0x3F, 0x15, 0x2F, 0x3F, 0x2F, 0x15, 0x3F, 0x2F, 0x2F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x15, 0x3F, 0x3F, 0x2F, 0x2F, 0x3F, 0x2F, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x3F, 0x15, 0x3F, 0x3F, 0x2F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F +}; + +uint16 *OldDOSFont::_cgaDitheringTable = 0; +int OldDOSFont::_numRef = 0; + +OldDOSFont::OldDOSFont(Common::RenderMode mode) : _renderMode(mode) { + _data = 0; + _width = _height = _numGlyphs = 0; + _bitmapOffsets = 0; + + _numRef++; + if (!_cgaDitheringTable && _numRef == 1) { + _cgaDitheringTable = new uint16[256]; + memset(_cgaDitheringTable, 0, 256 * sizeof(uint16)); + static const uint bits[] = { 0, 3, 12, 15 }; + for (int i = 0; i < 256; i++) + _cgaDitheringTable[i] = (bits[i & 3] << 8) | (bits[(i >> 2) & 3] << 12) | (bits[(i >> 4) & 3] << 0) | (bits[(i >> 6) & 3] << 4); + } +} + +OldDOSFont::~OldDOSFont() { + unload(); + + if (_numRef) + --_numRef; + + if (_cgaDitheringTable && !_numRef) { + delete[] _cgaDitheringTable; + _cgaDitheringTable = 0; + } +} + +bool OldDOSFont::load(Common::SeekableReadStream &file) { + unload(); + + _data = new uint8[file.size()]; + assert(_data); + + file.read(_data, file.size()); + if (file.err()) + return false; + + if (file.size() - 2 != READ_LE_UINT16(_data)) + return false; + + _width = _data[0x103]; + _height = _data[0x102]; + _numGlyphs = 255; + + _bitmapOffsets = (uint16 *)(_data + 2); + + for (int i = 0; i < _numGlyphs; ++i) + _bitmapOffsets[i] = READ_LE_UINT16(&_bitmapOffsets[i]); + + return true; +} + +int OldDOSFont::getCharWidth(uint16 c) const { + if (c >= _numGlyphs) + return 0; + return _width; +} + +void OldDOSFont::drawChar(uint16 c, byte *dst, int pitch, int bpp) const { + static const uint8 renderMaskTable6[] = { 0xFC, 0x00, 0x7E, 0x00, 0x3F, 0x00, 0x1F, 0x80, 0x0F, 0xC0, 0x07, 0xE0, 0x03, 0xF0, 0x01, 0xF8 }; + static const uint8 renderMaskTable8[] = { 0xFF, 0x00, 0x7F, 0x80, 0x3F, 0xC0, 0x1F, 0xE0, 0x0F, 0xF0, 0x07, 0xF8, 0x03, 0xFC, 0x01, 0xFE }; + + if (_width != 8 && _width != 6) + error("EOB font rendering not implemented for other font widths than 6 and 8."); + + if (_width == 6) { + switch (c) { + case 0x81: + case 0x9A: + c = 0x5D; + break; + case 0x84: + case 0x8E: + c = 0x5B; + break; + case 0x94: + case 0x99: + c = 0x40; + case 0xE1: + // TODO: recheck this: no conversion for 'ß' ? + break; + } + } else if (_width == 8) { + switch (c) { + case 0x81: + case 0x9A: + case 0x5D: + c = 0x1D; + break; + case 0x84: + case 0x5B: + c = 0x1E; + break; + case 0x94: + case 0x40: + c = 0x1F; + break; + case 0x8E: + c = 0x1B; + break; + case 0x99: + c = 0x1C; + break; + case 0xE1: + c = 0x19; + break; + } + } + + pitch *= bpp; + const uint8 *src = &_data[_bitmapOffsets[c]]; + uint8 *dst2 = dst + pitch; + + int w = (_width - 1) >> 3; + pitch -= _width * bpp; + + uint16 color1 = _colorMap8bit[1]; + uint16 color2 = _colorMap8bit[0]; + + if (bpp == 2) { + color1 = _colorMap16bit[1]; + color2 = _colorMap16bit[0]; + } else if (_renderMode == Common::kRenderCGA || _renderMode == Common::kRenderEGA) { + color1 &= 0x0F; + color2 &= 0x0F; + } + + static const uint16 cgaColorMask[] = { 0, 0x5555, 0xAAAA, 0xFFFF }; + uint16 cgaMask1 = cgaColorMask[color1 & 3]; + uint16 cgaMask2 = cgaColorMask[color2 & 3]; + + int cH = _height; + while (cH--) { + int cW = w; + uint8 last = 0; + const uint8 *mtbl = _width == 8 ? renderMaskTable8 : renderMaskTable6; + + if (_renderMode == Common::kRenderCGA) { + uint8 s = *src++; + uint8 m = *mtbl++; + + uint8 in = s | last; + uint16 cmp1 = 0; + uint16 cmp2 = 0; + + if (color1) { + in &= m; + cmp1 = _cgaDitheringTable[in]; + } + + if (color2) { + in = ~in & m; + cmp2 = _cgaDitheringTable[in]; + } + + uint16 cDst = 0; + uint8 sh = 6; + for (int i = 0; i < _width; i++) { + cDst |= ((dst[i] & 3) << sh); + sh = (sh - 2) & 0x0F; + } + + uint16 out = (~(cmp1 | cmp2) & cDst) | (cmp1 & cgaMask1) | (cmp2 & cgaMask2); + + sh = 6; + for (int i = 0; i < _width; i++) { + *dst++ = (out >> sh) & 3; + sh = (sh - 2) & 0x0F; + } + + last = s; + + } else { + for (bool runWidthLoop = true; runWidthLoop;) { + uint8 s = *src++; + uint8 m = *mtbl++; + + for (uint8 i = 0x80; i; i >>= 1) { + if (!(m & i)) { + runWidthLoop = false; + break; + } + + if (s & i) { + if (bpp == 2) + *(uint16*)dst = color1; + else if (color1) + *dst = color1; + } else { + if (bpp == 2) { + if (color2 != 0xFFFF) + *(uint16*)dst = color2; + } else if (color2) { + *dst = color2; + } + } + dst += bpp; + } + + if (cW) + cW--; + else + runWidthLoop = false; + } + } + + dst += pitch; + dst2 += pitch; + } +} + +void OldDOSFont::unload() { + delete[] _data; + _data = 0; + _width = _height = _numGlyphs = 0; + _bitmapOffsets = 0; +} + +SJISFontLarge::SJISFontLarge(Graphics::FontSJIS *font) : SJISFont(font, 0, false, false, false, 0) { + _sjisWidth = _font->getMaxFontWidth(); + _fontHeight = _font->getFontHeight(); + _asciiWidth = _font->getCharWidth('a'); +} + +void SJISFontLarge::drawChar(uint16 c, byte *dst, int pitch, int) const { + _font->drawChar(dst, c, 320, 1, _colorMap[1], _colorMap[0], 320, 200); +} + +SJISFont12x12::SJISFont12x12(const uint16 *searchTable) : _height(6), _width(6), _data(0) { + assert(searchTable); + for (int i = 0; i < 148; i++) + _searchTable[searchTable[i]] = i + 1; +} + +bool SJISFont12x12::load(Common::SeekableReadStream &file) { + delete[] _data; + int size = 148 * 24; + if (file.size() < size) + return false; + + _data = new uint8[size]; + file.read(_data, size); + + return true; +} + +void SJISFont12x12::unload() { + delete[] _data; + _data = 0; + _searchTable.clear(); +} + +void SJISFont12x12::drawChar(uint16 c, byte *dst, int pitch, int) const { + int offs = _searchTable[c]; + if (!offs) + return; + + const uint8 *src = _data + (offs - 1) * 24; + uint8 color1 = _colorMap[1]; + + int bt = 0; + uint16 chr = 0; + + for (int i = 0; i < 192; ++i) { + if (!bt) { + chr = *src++; + bt = 8; + } + if (chr & 0x80) + *dst = color1; + dst++; + if (--bt) + chr <<= 1; + else if (i & 8) + dst += (pitch - 16); + } +} + +} // End of namespace Kyra + +#endif // ENABLE_EOB diff --git a/engines/kyra/graphics/screen_eob.h b/engines/kyra/graphics/screen_eob.h new file mode 100644 index 0000000000..401a229045 --- /dev/null +++ b/engines/kyra/graphics/screen_eob.h @@ -0,0 +1,177 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_EOB_H +#define KYRA_SCREEN_EOB_H + +#ifdef ENABLE_EOB + +#include "kyra/graphics/screen.h" + +namespace Kyra { + +class EoBCoreEngine; +class Screen_EoB : public Screen { +public: + Screen_EoB(EoBCoreEngine *vm, OSystem *system); + virtual ~Screen_EoB(); + + bool init(); + + void setClearScreenDim(int dim); + void clearCurDim(); + + void setMouseCursor(int x, int y, const byte *shape); + void setMouseCursor(int x, int y, const byte *shape, const uint8 *ovl); + + void loadFileDataToPage(Common::SeekableReadStream *s, int pageNum, uint32 size); + + void printShadedText(const char *string, int x, int y, int col1, int col2); + + void loadEoBBitmap(const char *file, const uint8 *cgaMapping, int tempPage, int destPage, int convertToPage); + void loadShapeSetBitmap(const char *file, int tempPage, int destPage); + + void convertPage(int srcPage, int dstPage, const uint8 *cgaMapping); + + void setScreenPalette(const Palette &pal); + void getRealPalette(int num, uint8 *dst); + + uint8 *encodeShape(uint16 x, uint16 y, uint16 w, uint16 h, bool encode8bit = false, const uint8 *cgaMapping = 0); + void drawShape(uint8 pageNum, const uint8 *shapeData, int x, int y, int sd = -1, int flags = 0, ...); + const uint8 *scaleShape(const uint8 *shapeData, int blockDistance); + const uint8 *scaleShapeStep(const uint8 *shp); + const uint8 *generateShapeOverlay(const uint8 *shp, const uint8 *fadingTable); + + void setShapeFrame(int x1, int y1, int x2, int y2); + void enableShapeBackgroundFading(bool enable); + void setShapeFadingLevel(int val); + + void setGfxParameters(int x, int y, int col); + void drawExplosion(int scale, int radius, int numElements, int stepSize, int aspectRatio, const uint8 *colorTable, int colorTableSize); + void drawVortex(int numElements, int radius, int stepSize, int, int disorder, const uint8 *colorTable, int colorTableSize); + + void fadeTextColor(Palette *pal, int color1, int fadeTextColor); + bool delayedFadePalStep(Palette *fadePal, Palette *destPal, int rate); + + void setTextColorMap(const uint8 *cmap) {} + int getRectSize(int w, int h); + + void setFadeTable(const uint8 *table); + void createFadeTable(const uint8 *palData, uint8 *dst, uint8 rootColor, uint8 weight); + void createFadeTable16bit(const uint16 *palData, uint16 *dst, uint16 rootColor, uint8 weight); + + const uint16 *getCGADitheringTable(int index); + const uint8 *getEGADitheringTable(); + + bool loadFont(FontId fontId, const char *filename); + + // FM-Towns specific + void decodeSHP(const uint8 *data, int dstPage); + void convertToHiColor(int page); + void shadeRect(int x1, int y1, int x2, int y2, int shadingLevel); + +private: + void updateDirtyRects(); + void ditherRect(const uint8 *src, uint8 *dst, int dstPitch, int srcW, int srcH, int colorKey = -1); + + void drawShapeSetPixel(uint8 *dst, uint8 col); + void scaleShapeProcessLine2Bit(uint8 *&shpDst, const uint8 *&shpSrc, uint32 transOffsetDst, uint32 transOffsetSrc); + void scaleShapeProcessLine4Bit(uint8 *&dst, const uint8 *&src); + bool posWithinRect(int posX, int posY, int x1, int y1, int x2, int y2); + + void setPagePixel16bit(int pageNum, int x, int y, uint16 color); + + void generateEGADitheringTable(const Palette &pal); + void generateCGADitheringTables(const uint8 *mappingData); + + int _dsDiv, _dsRem, _dsScaleTrans; + uint8 *_cgaScaleTable; + int16 _gfxX, _gfxY; + uint16 _gfxCol; + const uint8 *_gfxMaxY; + + int16 _dsX1, _dsX2, _dsY1, _dsY2; + + bool _dsBackgroundFading; + int16 _dsBackgroundFadingXOffs; + uint8 _shapeOverlay[16]; + + uint8 *_dsTempPage; + uint8 *_shpBuffer; + uint8 *_convertHiColorBuffer; + + uint16 *_cgaDitheringTables[2]; + const uint8 *_cgaMappingDefault; + + uint8 *_egaDitheringTable; + uint8 *_egaDitheringTempPage; + + const uint16 _cursorColorKey16Bit; + + static const uint8 _egaMatchTable[]; + static const ScreenDim _screenDimTable[]; + static const int _screenDimTableCount; +}; + +/** +* SJIS Font variant used in the intro and outro of EOB II FM-Towns. It appears twice as large, since it is not rendered on the hires overlay pages +*/ +class SJISFontLarge : public SJISFont { +public: + SJISFontLarge(Graphics::FontSJIS *font); + virtual ~SJISFontLarge() { unload(); } + + virtual bool usesOverlay() const { return false; } + virtual void drawChar(uint16 c, byte *dst, int pitch, int) const; +}; + +/** +* 12 x 12 SJIS font for EOB II FM-Towns. The data for this font comes from a file, not from the font rom. +*/ +class SJISFont12x12 : public Font { +public: + SJISFont12x12(const uint16 *searchTable); + virtual ~SJISFont12x12() { unload(); } + + virtual bool load(Common::SeekableReadStream &file); + virtual bool usesOverlay() const { return true; } + virtual int getHeight() const { return _height; } + virtual int getWidth() const { return _width; } + virtual int getCharWidth(uint16 c) const { return _width; } + virtual void setColorMap(const uint8 *src) { _colorMap = src; } + virtual void drawChar(uint16 c, byte *dst, int pitch, int) const; + +private: + void unload(); + + uint8 *_data; + Common::HashMap _searchTable; + + const uint8 *_colorMap; + const int _height, _width; +}; + +} // End of namespace Kyra + +#endif // ENABLE_EOB + +#endif diff --git a/engines/kyra/graphics/screen_hof.cpp b/engines/kyra/graphics/screen_hof.cpp new file mode 100644 index 0000000000..a3ab811cdb --- /dev/null +++ b/engines/kyra/graphics/screen_hof.cpp @@ -0,0 +1,96 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/screen_hof.h" +#include "kyra/engine/kyra_hof.h" + +namespace Kyra { + +Screen_HoF::Screen_HoF(KyraEngine_HoF *vm, OSystem *system) + : Screen_v2(vm, system, _screenDimTable, _screenDimTableCount), _vm(vm) { +} + +void Screen_HoF::generateGrayOverlay(const Palette &srcPal, uint8 *grayOverlay, int factor, int addR, int addG, int addB, int lastColor, bool flag) { + Palette tmpPal(lastColor); + + for (int i = 0; i != lastColor; i++) { + if (flag) { + int v = ((((srcPal[3 * i] & 0x3F) + (srcPal[3 * i + 1] & 0x3F) + + (srcPal[3 * i + 2] & 0x3F)) / 3) * factor) / 0x40; + tmpPal[3 * i] = tmpPal[3 * i + 1] = tmpPal[3 * i + 2] = v & 0xFF; + } else { + int v = (((srcPal[3 * i] & 0x3F) * factor) / 0x40) + addR; + tmpPal[3 * i] = (v > 0x3F) ? 0x3F : v & 0xFF; + v = (((srcPal[3 * i + 1] & 0x3F) * factor) / 0x40) + addG; + tmpPal[3 * i + 1] = (v > 0x3F) ? 0x3F : v & 0xFF; + v = (((srcPal[3 * i + 2] & 0x3F) * factor) / 0x40) + addB; + tmpPal[3 * i + 2] = (v > 0x3F) ? 0x3F : v & 0xFF; + } + } + + for (int i = 0; i < lastColor; i++) + grayOverlay[i] = findLeastDifferentColor(tmpPal.getData() + 3 * i, srcPal, 0, lastColor); +} + +void Screen_HoF::cmpFadeFrameStep(int srcPage, int srcW, int srcH, int srcX, int srcY, int dstPage, int dstW, + int dstH, int dstX, int dstY, int cmpW, int cmpH, int cmpPage) { + + if (!cmpW || !cmpH) + return; + + int r1, r2, r3, r4, r5, r6; + + int X1 = srcX; + int Y1 = srcY; + int W1 = cmpW; + int H1 = cmpH; + + if (!calcBounds(srcW, srcH, X1, Y1, W1, H1, r1, r2, r3)) + return; + + int X2 = dstX; + int Y2 = dstY; + int W2 = W1; + int H2 = H1; + + if (!calcBounds(dstW, dstH, X2, Y2, W2, H2, r4, r5, r6)) + return; + + const uint8 *src = getPagePtr(srcPage) + srcW * (Y1 + r5); + uint8 *dst = getPagePtr(dstPage) + dstW * (Y2 + r2); + const uint8 *cmp = getPagePtr(cmpPage); + + while (H2--) { + const uint8 *s = src + r4 + X1; + uint8 *d = dst + r1 + X2; + + for (int i = 0; i < W2; i++) { + int ix = (*s++ << 8) + *d; + *d++ = cmp[ix]; + } + + src += W1; + dst += W2; + } +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen_hof.h b/engines/kyra/graphics/screen_hof.h new file mode 100644 index 0000000000..5cb189e0ef --- /dev/null +++ b/engines/kyra/graphics/screen_hof.h @@ -0,0 +1,50 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_HOF_H +#define KYRA_SCREEN_HOF_H + +#include "kyra/graphics/screen_v2.h" + +namespace Kyra { + +class KyraEngine_HoF; + +class Screen_HoF : public Screen_v2 { +friend class Debugger_v2; +public: + Screen_HoF(KyraEngine_HoF *vm, OSystem *system); + + // sequence player + void generateGrayOverlay(const Palette &pal, uint8 *grayOverlay, int factor, int addR, int addG, int addB, int lastColor, bool flag); + void cmpFadeFrameStep(int srcPage, int srcW, int srcH, int srcX, int srcY, int dstPage, int dstW, int dstH, int dstX, int dstY, int cmpW, int cmpH, int cmpPage); + +private: + KyraEngine_HoF *_vm; + + static const ScreenDim _screenDimTable[]; + static const int _screenDimTableCount; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/screen_lok.cpp b/engines/kyra/graphics/screen_lok.cpp new file mode 100644 index 0000000000..114382b487 --- /dev/null +++ b/engines/kyra/graphics/screen_lok.cpp @@ -0,0 +1,447 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/screen_lok.h" +#include "kyra/engine/kyra_lok.h" + +#include "common/system.h" + +#include "graphics/palette.h" + +namespace Kyra { + +Screen_LoK::Screen_LoK(KyraEngine_LoK *vm, OSystem *system) + : Screen(vm, system, _screenDimTable, _screenDimTableCount) { + _vm = vm; + _unkPtr1 = _unkPtr2 = 0; + _bitBlitNum = 0; +} + +Screen_LoK::~Screen_LoK() { + for (int i = 0; i < ARRAYSIZE(_saveLoadPage); ++i) { + delete[] _saveLoadPage[i]; + _saveLoadPage[i] = 0; + } + + for (int i = 0; i < ARRAYSIZE(_saveLoadPageOvl); ++i) { + delete[] _saveLoadPageOvl[i]; + _saveLoadPageOvl[i] = 0; + } + + delete[] _unkPtr1; + delete[] _unkPtr2; +} + +bool Screen_LoK::init() { + if (!Screen::init()) + return false; + + memset(_bitBlitRects, 0, sizeof(_bitBlitRects)); + _bitBlitNum = 0; + memset(_saveLoadPage, 0, sizeof(_saveLoadPage)); + memset(_saveLoadPageOvl, 0, sizeof(_saveLoadPageOvl)); + + _unkPtr1 = new uint8[getRectSize(1, 144)]; + assert(_unkPtr1); + memset(_unkPtr1, 0, getRectSize(1, 144)); + _unkPtr2 = new uint8[getRectSize(1, 144)]; + assert(_unkPtr2); + memset(_unkPtr2, 0, getRectSize(1, 144)); + + return true; +} + +void Screen_LoK::fadeSpecialPalette(int palIndex, int startIndex, int size, int fadeTime) { + if (_vm->gameFlags().platform == Common::kPlatformAmiga) + return; + + assert(_vm->palTable1()[palIndex]); + + Palette tempPal(getPalette(0).getNumColors()); + tempPal.copy(getPalette(0)); + tempPal.copy(_vm->palTable1()[palIndex], 0, size, startIndex); + + fadePalette(tempPal, fadeTime * 18); + + getPalette(0).copy(tempPal, startIndex, size); + setScreenPalette(getPalette(0)); + _system->updateScreen(); +} + +void Screen_LoK::addBitBlitRect(int x, int y, int w, int h) { + if (_bitBlitNum >= kNumBitBlitRects) + error("too many bit blit rects"); + + _bitBlitRects[_bitBlitNum].left = x; + _bitBlitRects[_bitBlitNum].top = y; + _bitBlitRects[_bitBlitNum].right = x + w; + _bitBlitRects[_bitBlitNum].bottom = y + h; + ++_bitBlitNum; +} + +void Screen_LoK::bitBlitRects() { + Common::Rect *cur = _bitBlitRects; + while (_bitBlitNum) { + _bitBlitNum--; + copyRegion(cur->left, cur->top, cur->left, cur->top, cur->width(), cur->height(), 2, 0); + ++cur; + } +} + +void Screen_LoK::savePageToDisk(const char *file, int page) { + if (!_saveLoadPage[page / 2]) { + _saveLoadPage[page / 2] = new uint8[SCREEN_W * SCREEN_H]; + assert(_saveLoadPage[page / 2]); + } + memcpy(_saveLoadPage[page / 2], getPagePtr(page), SCREEN_W * SCREEN_H); + + if (_useOverlays) { + if (!_saveLoadPageOvl[page / 2]) { + _saveLoadPageOvl[page / 2] = new uint8[SCREEN_OVL_SJIS_SIZE]; + assert(_saveLoadPageOvl[page / 2]); + } + + uint8 *srcPage = getOverlayPtr(page); + if (!srcPage) { + warning("trying to save unsupported overlay page %d", page); + return; + } + + memcpy(_saveLoadPageOvl[page / 2], srcPage, SCREEN_OVL_SJIS_SIZE); + } +} + +void Screen_LoK::loadPageFromDisk(const char *file, int page) { + if (!_saveLoadPage[page / 2]) { + warning("trying to restore page %d, but no backup found", page); + return; + } + + copyBlockToPage(page, 0, 0, SCREEN_W, SCREEN_H, _saveLoadPage[page / 2]); + delete[] _saveLoadPage[page / 2]; + _saveLoadPage[page / 2] = 0; + + if (_saveLoadPageOvl[page / 2]) { + uint8 *dstPage = getOverlayPtr(page); + if (!dstPage) { + warning("trying to restore unsupported overlay page %d", page); + return; + } + + memcpy(dstPage, _saveLoadPageOvl[page / 2], SCREEN_OVL_SJIS_SIZE); + delete[] _saveLoadPageOvl[page / 2]; + _saveLoadPageOvl[page / 2] = 0; + } +} + +void Screen_LoK::queryPageFromDisk(const char *file, int page, uint8 *buffer) { + if (!_saveLoadPage[page / 2]) { + warning("trying to query page %d, but no backup found", page); + return; + } + + memcpy(buffer, _saveLoadPage[page / 2], SCREEN_W * SCREEN_H); +} + +void Screen_LoK::deletePageFromDisk(int page) { + delete[] _saveLoadPage[page / 2]; + _saveLoadPage[page / 2] = 0; + + if (_saveLoadPageOvl[page / 2]) { + delete[] _saveLoadPageOvl[page / 2]; + _saveLoadPageOvl[page / 2] = 0; + } +} + +void Screen_LoK::copyBackgroundBlock(int x, int page, int flag) { + if (x < 1) + return; + + int height = 128; + if (flag) + height += 8; + if (!(x & 1)) + ++x; + if (x == 19) + x = 17; + + uint8 *ptr1 = _unkPtr1; + uint8 *ptr2 = _unkPtr2; + int oldVideoPage = _curPage; + _curPage = page; + + int curX = x; + copyRegionToBuffer(_curPage, 8, 8, 8, height, ptr2); + for (int i = 0; i < 19; ++i) { + int tempX = curX + 1; + copyRegionToBuffer(_curPage, tempX << 3, 8, 8, height, ptr1); + copyBlockToPage(_curPage, tempX << 3, 8, 8, height, ptr2); + int newXPos = curX + x; + if (newXPos > 37) + newXPos = newXPos % 38; + + tempX = newXPos + 1; + copyRegionToBuffer(_curPage, tempX << 3, 8, 8, height, ptr2); + copyBlockToPage(_curPage, tempX << 3, 8, 8, height, ptr1); + curX += x * 2; + if (curX > 37) { + curX = curX % 38; + } + } + _curPage = oldVideoPage; +} + +void Screen_LoK::copyBackgroundBlock2(int x) { + copyBackgroundBlock(x, 4, 1); +} + +void Screen_LoK::setTextColorMap(const uint8 *cmap) { + setTextColor(cmap, 0, 11); +} + +int Screen_LoK::getRectSize(int x, int y) { + if (x < 1) + x = 1; + else if (x > 40) + x = 40; + + if (y < 1) + y = 1; + else if (y > 200) + y = 200; + + return ((x * y) << 3); +} + +void Screen_LoK::postProcessCursor(uint8 *data, int width, int height, int pitch) { + if (_vm->gameFlags().platform == Common::kPlatformAmiga && _interfacePaletteEnabled) { + pitch -= width; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (*data != _cursorColorKey) + *data += 32; + ++data; + } + + data += pitch; + } + } +} + +#pragma mark - + +Screen_LoK_16::Screen_LoK_16(KyraEngine_LoK *vm, OSystem *system) : Screen_LoK(vm, system) { + memset(_paletteDither, 0, sizeof(_paletteDither)); +} + +void Screen_LoK_16::setScreenPalette(const Palette &pal) { + _screenPalette->copy(pal); + + for (int i = 0; i < 256; ++i) + paletteMap(i, pal[i * 3 + 0] << 2, pal[i * 3 + 1] << 2, pal[i * 3 + 2] << 2); + + set16ColorPalette(_palette16); + _forceFullUpdate = true; +} + +void Screen_LoK_16::fadePalette(const Palette &pal, int delay, const UpdateFunctor *upFunc) { + uint8 notBlackFlag = 0; + for (int i = 0; i < 768; ++i) { + if ((*_screenPalette)[i]) + notBlackFlag |= 1; + if (pal[i]) + notBlackFlag |= 2; + } + + if (notBlackFlag == 1 || notBlackFlag == 2) { + bool upFade = false; + + for (int i = 0; i < 768; ++i) { + if ((*_screenPalette)[i] < pal[i]) { + upFade = true; + break; + } + } + + if (upFade) { + for (int i = 0; i < 256; ++i) + paletteMap(i, pal[i * 3 + 0] << 2, pal[i * 3 + 1] << 2, pal[i * 3 + 2] << 2); + _forceFullUpdate = true; + } + + uint8 color16Palette[16 * 3]; + + if (upFade) + memset(color16Palette, 0, sizeof(color16Palette)); + else + memcpy(color16Palette, _palette16, sizeof(color16Palette)); + + set16ColorPalette(color16Palette); + updateScreen(); + + for (int i = 0; i < 16; ++i) { + set16ColorPalette(color16Palette); + + for (int k = 0; k < 48; ++k) { + if (upFade) { + if (color16Palette[k] < _palette16[k]) + ++color16Palette[k]; + } else { + if (color16Palette[k] > 0) + --color16Palette[k]; + } + } + + if (upFunc && upFunc->isValid()) + (*upFunc)(); + else + _system->updateScreen(); + + _vm->delay((delay >> 5) * _vm->tickLength()); + } + } + + setScreenPalette(pal); +} + +void Screen_LoK_16::getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff) { + error("Screen_LoK_16::getFadeParams called"); +} + +int Screen_LoK_16::fadePalStep(const Palette &pal, int diff) { + error("Screen_LoK_16::fadePalStep called"); + return 0; // for compilers that don't support NORETURN +} + +void Screen_LoK_16::paletteMap(uint8 idx, int r, int g, int b) { + const int red = r; + const int green = g; + const int blue = b; + + uint16 rgbDiff = 1000; + int rDiff = 0, gDiff = 0, bDiff = 0; + + int index1 = -1; + + for (int i = 0; i < 16; ++i) { + const int realR = _palette16[i * 3 + 0] << 4; + const int realG = _palette16[i * 3 + 1] << 4; + const int realB = _palette16[i * 3 + 2] << 4; + + uint16 diff = ABS(r - realR) + ABS(g - realG) + ABS(b - realB); + + if (diff < rgbDiff) { + rgbDiff = diff; + index1 = i; + + rDiff = r - realR; + gDiff = g - realG; + bDiff = b - realB; + } + } + + r = rDiff / 4 + red; + g = gDiff / 4 + green; + b = bDiff / 4 + blue; + + rgbDiff = 1000; + int index2 = -1; + + for (int i = 0; i < 16; ++i) { + const int realR = _palette16[i * 3 + 0] << 4; + const int realG = _palette16[i * 3 + 1] << 4; + const int realB = _palette16[i * 3 + 2] << 4; + + uint16 diff = ABS(r - realR) + ABS(g - realG) + ABS(b - realB); + + if (diff < rgbDiff) { + rgbDiff = diff; + index2 = i; + } + } + + _paletteDither[idx].bestMatch = index1; + _paletteDither[idx].invertMatch = index2; +} + +void Screen_LoK_16::convertTo16Colors(uint8 *page, int w, int h, int pitch, int keyColor) { + const int rowAdd = pitch * 2 - w; + + uint8 *row1 = page; + uint8 *row2 = page + pitch; + + for (int i = 0; i < h; i += 2) { + for (int k = 0; k < w; k += 2) { + if (keyColor == -1 || keyColor != *row1) { + const PaletteDither &dither = _paletteDither[*row1]; + + *row1++ = dither.bestMatch; + *row1++ = dither.invertMatch; + *row2++ = dither.invertMatch; + *row2++ = dither.bestMatch; + } else { + row1 += 2; + row2 += 2; + } + } + + row1 += rowAdd; + row2 += rowAdd; + } +} + +void Screen_LoK_16::mergeOverlay(int x, int y, int w, int h) { + byte *dst = _sjisOverlayPtrs[0] + y * 640 + x; + + // We do a game screen rect to 16 color dithering here. It is + // important that we do not dither the overlay, since else the + // japanese fonts will look wrong. + convertTo16Colors(dst, w, h, 640); + + const byte *src = _sjisOverlayPtrs[1] + y * 640 + x; + + int add = 640 - w; + + while (h--) { + for (x = 0; x < w; ++x, ++dst) { + byte col = *src++; + if (col != _sjisInvisibleColor) + *dst = _paletteDither[col].bestMatch; + } + dst += add; + src += add; + } +} + +void Screen_LoK_16::set16ColorPalette(const uint8 *pal) { + uint8 palette[16 * 3]; + for (int i = 0; i < 16; ++i) { + palette[i * 3 + 0] = (pal[i * 3 + 0] * 0xFF) / 0x0F; + palette[i * 3 + 1] = (pal[i * 3 + 1] * 0xFF) / 0x0F; + palette[i * 3 + 2] = (pal[i * 3 + 2] * 0xFF) / 0x0F; + } + + _system->getPaletteManager()->setPalette(palette, 0, 16); +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen_lok.h b/engines/kyra/graphics/screen_lok.h new file mode 100644 index 0000000000..3a4ebfd6a4 --- /dev/null +++ b/engines/kyra/graphics/screen_lok.h @@ -0,0 +1,111 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_LOK_H +#define KYRA_SCREEN_LOK_H + +#include "kyra/graphics/screen.h" + +namespace Kyra { + +class KyraEngine_LoK; + +class Screen_LoK : public Screen { +public: + Screen_LoK(KyraEngine_LoK *vm, OSystem *system); + virtual ~Screen_LoK(); + + bool init(); + + int getRectSize(int w, int h); + + void setTextColorMap(const uint8 *cmap); + + void fadeSpecialPalette(int palIndex, int startIndex, int size, int fadeTime); + + void savePageToDisk(const char *file, int page); + void loadPageFromDisk(const char *file, int page); + void queryPageFromDisk(const char *file, int page, uint8 *buffer); + void deletePageFromDisk(int page); + + void copyBackgroundBlock(int x, int page, int flag); + void copyBackgroundBlock2(int x); + + void addBitBlitRect(int x, int y, int w, int h); + void bitBlitRects(); + + // AMIGA specific + virtual void postProcessCursor(uint8 *data, int width, int height, int pitch); + +protected: + enum { + kNumBitBlitRects = 10 + }; + + KyraEngine_LoK *_vm; + + static const ScreenDim _screenDimTable[]; + static const int _screenDimTableCount; + + Common::Rect _bitBlitRects[kNumBitBlitRects]; + int _bitBlitNum; + uint8 *_unkPtr1, *_unkPtr2; + + uint8 *_saveLoadPage[8]; + uint8 *_saveLoadPageOvl[8]; +}; + +class Screen_LoK_16 : public Screen_LoK { +public: + Screen_LoK_16(KyraEngine_LoK *vm, OSystem *system); + + void setScreenPalette(const Palette &pal); + + void fadePalette(const Palette &pal, int delay, const UpdateFunctor *upFunc = 0); + void getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff); + int fadePalStep(const Palette &pal, int diff); +private: + void updateDirtyRectsOvl(); + + void convertTo16Colors(uint8 *page, int w, int h, int pitch, int keyColor = -1); + void postProcessCursor(uint8 *data, int width, int height, int pitch) { + convertTo16Colors(data, width, height, pitch, _cursorColorKey); + } + void mergeOverlay(int x, int y, int w, int h); + + void set16ColorPalette(const uint8 *pal); + + void paletteMap(uint8 idx, int r, int g, int b); + + struct PaletteDither { + uint8 bestMatch; + uint8 invertMatch; + }; + + PaletteDither _paletteDither[256]; + + static const uint8 _palette16[48]; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/screen_lol.cpp b/engines/kyra/graphics/screen_lol.cpp new file mode 100644 index 0000000000..b42565fc9d --- /dev/null +++ b/engines/kyra/graphics/screen_lol.cpp @@ -0,0 +1,893 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_LOL + +#include "kyra/graphics/screen_lol.h" +#include "kyra/engine/lol.h" + +#include "common/system.h" + +#include "graphics/palette.h" + +namespace Kyra { + +Screen_LoL::Screen_LoL(LoLEngine *vm, OSystem *system) : Screen_v2(vm, system, vm->gameFlags().use16ColorMode ? _screenDimTable16C : _screenDimTable256C, _screenDimTableCount) { + _paletteOverlay1 = new uint8[0x100]; + _paletteOverlay2 = new uint8[0x100]; + _grayOverlay = new uint8[0x100]; + memset(_paletteOverlay1, 0, 0x100); + memset(_paletteOverlay2, 0, 0x100); + memset(_grayOverlay, 0, 0x100); + + for (int i = 0; i < 8; i++) + _levelOverlays[i] = new uint8[256]; + + _fadeFlag = 2; +} + +Screen_LoL::~Screen_LoL() { + for (int i = 0; i < 8; i++) + delete[] _levelOverlays[i]; + + delete[] _paletteOverlay1; + delete[] _paletteOverlay2; + delete[] _grayOverlay; +} + +void Screen_LoL::fprintString(const char *format, int x, int y, uint8 col1, uint8 col2, uint flags, ...) { + if (!format) + return; + + char string[240]; + va_list vaList; + va_start(vaList, flags); + vsnprintf(string, sizeof(string), format, vaList); + va_end(vaList); + + if (flags & 1) + x -= (getTextWidth(string) >> 1); + + if (flags & 2) + x -= getTextWidth(string); + + if (_use16ColorMode) { + if (flags & 12) { + printText(string, x - 1, y, 0x44, col2); + printText(string, x, y + 1, 0x44, col2); + } + } else { + if (flags & 4) { + printText(string, x - 1, y, 1, col2); + printText(string, x, y + 1, 1, col2); + } + + if (flags & 8) { + printText(string, x - 1, y, 227, col2); + printText(string, x, y + 1, 227, col2); + } + } + + printText(string, x, y, col1, col2); +} + +void Screen_LoL::fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint flags, ...) { + char buffer[400]; + + va_list args; + va_start(args, flags); + vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + + if ((flags & 0x0F00) == 0x100) + x -= getTextWidth(buffer) >> 1; + if ((flags & 0x0F00) == 0x200) + x -= getTextWidth(buffer); + + if ((flags & 0x00F0) == 0x20) { + printText(buffer, x - 1, y, c3, c2); + printText(buffer, x, y + 1, c3, c2); + } + + printText(buffer, x, y, c1, c2); +} + +void Screen_LoL::drawShadedBox(int x1, int y1, int x2, int y2, int color1, int color2) { + assert(x1 >= 0 && y1 >= 0); + hideMouse(); + + fillRect(x1, y1, x2, y1 + 1, color1); + fillRect(x1, y1, x1 + 1, y2, color1); + + drawClippedLine(x2, y1, x2, y2, color2); + drawClippedLine(x2 - 1, y1 + 1, x2 - 1, y2 - 1, color2); + drawClippedLine(x1 + 1, y2 - 1, x2, y2 - 1, color2); + drawClippedLine(x1, y2, x2, y2, color2); + + if (_use16ColorMode && color1 > color2) + drawBox(x1, y1, x2, y2, 0x44); + + showMouse(); +} + +void Screen_LoL::generateGrayOverlay(const Palette &srcPal, uint8 *grayOverlay, int factor, int addR, int addG, int addB, int lastColor, bool skipSpecialColors) { + Palette tmpPal(lastColor); + + for (int i = 0; i != lastColor; i++) { + int v = (((srcPal[3 * i] & 0x3F) * factor) / 0x40) + addR; + tmpPal[3 * i] = (v > 0x3F) ? 0x3F : v & 0xFF; + v = (((srcPal[3 * i + 1] & 0x3F) * factor) / 0x40) + addG; + tmpPal[3 * i + 1] = (v > 0x3F) ? 0x3F : v & 0xFF; + v = (((srcPal[3 * i + 2] & 0x3F) * factor) / 0x40) + addB; + tmpPal[3 * i + 2] = (v > 0x3F) ? 0x3F : v & 0xFF; + } + + for (int i = 0; i < lastColor; i++) + grayOverlay[i] = findLeastDifferentColor(tmpPal.getData() + 3 * i, srcPal, 0, lastColor, skipSpecialColors); +} + +void Screen_LoL::createTransparencyTablesIntern(const uint8 *ovl, int a, const uint8 *fxPal1, const uint8 *fxPal2, uint8 *outTable1, uint8 *outTable2, int b) { + Palette screenPal(256); + screenPal.copy(fxPal2, 0, 256); + + memset(outTable1, 0xFF, 256); + + for (int i = 0; i < a; i++) + outTable1[ovl[i]] = i; + + for (int i = 0; i < a; i++) { + if (ovl[i]) { + uint8 tcol[3]; + uint16 fcol[3]; + uint16 scol[3]; + + uint16 t1 = (b << 6) / 100; + uint16 t2 = 64 - t1; + + uint8 c = ovl[i]; + fcol[0] = fxPal1[3 * c]; + fcol[1] = fxPal1[3 * c + 1]; + fcol[2] = fxPal1[3 * c + 2]; + + uint8 *o = &outTable2[i << 8]; + + for (int ii = 0; ii < 256; ii++) { + scol[0] = screenPal[3 * ii]; + scol[1] = screenPal[3 * ii + 1]; + scol[2] = screenPal[3 * ii + 2]; + + tcol[0] = CLIP(((fcol[0] * t2) >> 6) + ((scol[0] * t1) >> 6), 0, 63); + tcol[1] = CLIP(((fcol[1] * t2) >> 6) + ((scol[1] * t1) >> 6), 0, 63); + tcol[2] = CLIP(((fcol[2] * t2) >> 6) + ((scol[2] * t1) >> 6), 0, 63); + + o[ii] = findLeastDifferentColor(tcol, screenPal, 0, 255); + } + + } else { + memset(&outTable2[i << 8], 0, 256); + } + } +} + +void Screen_LoL::drawGridBox(int x, int y, int w, int h, int col) { + if (w <= 0 || x >= 320 || h <= 0 || y >= 200) + return; + + if (x < 0) { + x += w; + if (x <= 0) + return; + w = x; + x = 0; + } + + int tmp = x + w; + if (tmp >= 320) { + w = 320 - x; + } + + int pitch = 320 - w; + + if (y < 0) { + y += h; + if (y <= 0) + return; + h = y; + y = 0; + } + + tmp = y + h; + if (tmp >= 200) { + h = 200 - y; + } + + tmp = (y + x) & 1; + uint8 *p = getPagePtr(_curPage) + y * 320 + x; + uint8 s = (tmp >> 8) & 1; + + w >>= 1; + int w2 = w; + + while (h--) { + if (w) { + while (w--) { + *(p + tmp) = col; + p += 2; + } + } + + if (s == 1) { + if (tmp == 0) + *p = col; + p++; + } + tmp ^= 1; + p += pitch; + w = w2; + } +} + +void Screen_LoL::fadeClearSceneWindow(int delay) { + if (_fadeFlag == 1) + return; + + if (_use16ColorMode) { + fadeToBlack(delay); + fillRect(112, 0, 288, 120, 0x44); + } else { + Palette tpal(getPalette(0).getNumColors()); + tpal.copy(getPalette(0), 128); + + loadSpecialColors(tpal); + fadePalette(tpal, delay); + + fillRect(112, 0, 288, 120, 0); + } + + _fadeFlag = 1; +} + +void Screen_LoL::backupSceneWindow(int srcPageNum, int dstPageNum) { + uint8 *src = getPagePtr(srcPageNum) + 112; + uint8 *dst = getPagePtr(dstPageNum) + 0xA500; + + for (int h = 0; h < 120; h++) { + for (int w = 0; w < 176; w++) + *dst++ = *src++; + src += 144; + } +} + +void Screen_LoL::restoreSceneWindow(int srcPageNum, int dstPageNum) { + uint8 *src = getPagePtr(srcPageNum) + 0xA500; + uint8 *dst = getPagePtr(dstPageNum) + 112; + + for (int h = 0; h < 120; h++) { + memcpy(dst, src, 176); + src += 176; + dst += 320; + } + + if (!dstPageNum) + addDirtyRect(112, 0, 176, 120); +} + +void Screen_LoL::clearGuiShapeMemory(int pageNum) { + uint8 *dst = getPagePtr(pageNum) + 0x79B0; + for (int i = 0; i < 23; i++) { + memset(dst, 0, 176); + dst += 320; + } +} + +void Screen_LoL::copyGuiShapeFromSceneBackupBuffer(int srcPageNum, int dstPageNum) { + uint8 *src = getPagePtr(srcPageNum) + 0x79C3; + uint8 *dst = getPagePtr(dstPageNum); + + for (int i = 0; i < 23; i++) { + uint8 len = 0; + uint8 v = 0; + + do { + v = *src++; + len++; + } while (!v); + + *dst++ = len; + + len = 69 - len; + memcpy(dst, src, len); + src += (len + 251); + dst += len; + } +} + +void Screen_LoL::copyGuiShapeToSurface(int srcPageNum, int dstPageNum) { + uint8 *src = getPagePtr(srcPageNum); + uint8 *dst = getPagePtr(dstPageNum) + 0xE7C3; + + for (int i = 0; i < 23; i++) { + uint8 v = *src++; + uint8 len = 69 - v; + dst += v; + memcpy(dst, src, len); + src += (len - 1); + dst += len; + + for (int ii = 0; ii < len; ii++) + *dst++ = *src--; + + src += (len + 1); + dst += (v + 38); + } +} + +void Screen_LoL::smoothScrollZoomStepTop(int srcPageNum, int dstPageNum, int x, int y) { + uint8 *src = getPagePtr(srcPageNum) + 0xA500 + y * 176 + x; + uint8 *dst = getPagePtr(dstPageNum) + 0xA500; + + x <<= 1; + uint16 width = 176 - x; + uint16 scaleX = (((x + 1) << 8) / width + 0x100); + uint16 cntW = scaleX >> 8; + scaleX <<= 8; + width--; + uint16 widthCnt = width; + + uint16 height = 46 - y; + uint16 scaleY = (((y + 1) << 8) / height + 0x100); + scaleY <<= 8; + + uint32 scaleYc = 0; + while (height) { + uint32 scaleXc = 0; + do { + scaleXc += scaleX; + int numbytes = cntW + (scaleXc >> 16); + scaleXc &= 0xFFFF; + memset(dst, *src++, numbytes); + dst += numbytes; + } while (--widthCnt); + + *dst++ = *src++; + widthCnt = width; + + src += x; + scaleYc += scaleY; + + if (scaleYc >> 16) { + scaleYc = 0; + src -= 176; + continue; + } + + height--; + } +} + +void Screen_LoL::smoothScrollZoomStepBottom(int srcPageNum, int dstPageNum, int x, int y) { + uint8 *src = getPagePtr(srcPageNum) + 0xC4A0 + x; + uint8 *dst = getPagePtr(dstPageNum) + 0xC4A0; + + x <<= 1; + uint16 width = 176 - x; + uint16 scaleX = (((x + 1) << 8) / width + 0x100); + uint16 cntW = scaleX >> 8; + scaleX <<= 8; + width--; + uint16 widthCnt = width; + + uint16 height = 74 - y; + uint16 scaleY = (((y + 1) << 8) / height + 0x100); + scaleY <<= 8; + + uint32 scaleYc = 0; + while (height) { + uint32 scaleXc = 0; + do { + scaleXc += scaleX; + int numbytes = cntW + (scaleXc >> 16); + scaleXc &= 0xFFFF; + memset(dst, *src++, numbytes); + dst += numbytes; + } while (--widthCnt); + + *dst++ = *src++; + widthCnt = width; + + src += x; + scaleYc += scaleY; + + if (scaleYc >> 16) { + scaleYc = 0; + src -= 176; + continue; + } + + height--; + } +} + +void Screen_LoL::smoothScrollHorizontalStep(int pageNum, int srcX, int dstX, int w) { + uint8 *d = getPagePtr(pageNum); + uint8 *s = d + 112 + srcX; + + int w2 = srcX + w - dstX; + int pitchS = 320 + w2 - (w << 1); + + int pitchD = 320 - w; + int h = 120; + + while (h--) { + for (int i = 0; i < w; i++) + *d++ = *s++; + d -= w; + s -= w2; + + for (int i = 0; i < w; i++) + *s++ = *d++; + + s += pitchS; + d += pitchD; + } +} + +void Screen_LoL::smoothScrollTurnStep1(int srcPage1Num, int srcPage2Num, int dstPageNum) { + uint8 *s = getPagePtr(srcPage1Num) + 273; + uint8 *d = getPagePtr(dstPageNum) + 0xA500; + + for (int i = 0; i < 120; i++) { + uint8 a = *s++; + *d++ = a; + *d++ = a; + + for (int ii = 0; ii < 14; ii++) { + a = *s++; + *d++ = a; + *d++ = a; + *d++ = a; + } + + s += 305; + d += 132; + } + + s = getPagePtr(srcPage2Num) + 112; + d = getPagePtr(dstPageNum) + 0xA52C; + + for (int i = 0; i < 120; i++) { + for (int ii = 0; ii < 33; ii++) { + *d++ = *s++; + *d++ = *s++; + uint8 a = *s++; + *d++ = a; + *d++ = a; + } + + s += 221; + d += 44; + } +} + +void Screen_LoL::smoothScrollTurnStep2(int srcPage1Num, int srcPage2Num, int dstPageNum) { + uint8 *s = getPagePtr(srcPage1Num) + 244; + uint8 *d = getPagePtr(dstPageNum) + 0xA500; + + for (int k = 0; k < 2; k++) { + for (int i = 0; i < 120; i++) { + for (int ii = 0; ii < 44; ii++) { + uint8 a = *s++; + *d++ = a; + *d++ = a; + } + + s += 276; + d += 88; + } + + s = getPagePtr(srcPage2Num) + 112; + d = getPagePtr(dstPageNum) + 0xA558; + } +} + +void Screen_LoL::smoothScrollTurnStep3(int srcPage1Num, int srcPage2Num, int dstPageNum) { + uint8 *s = getPagePtr(srcPage1Num) + 189; + uint8 *d = getPagePtr(dstPageNum) + 0xA500; + + for (int i = 0; i < 120; i++) { + for (int ii = 0; ii < 33; ii++) { + *d++ = *s++; + *d++ = *s++; + uint8 a = *s++; + *d++ = a; + *d++ = a; + } + + s += 221; + d += 44; + } + + s = getPagePtr(srcPage2Num) + 112; + d = getPagePtr(dstPageNum) + 0xA584; + + for (int i = 0; i < 120; i++) { + for (int ii = 0; ii < 14; ii++) { + uint8 a = *s++; + *d++ = a; + *d++ = a; + *d++ = a; + } + + uint8 a = *s++; + *d++ = a; + *d++ = a; + + s += 305; + d += 132; + } +} + +void Screen_LoL::copyRegionSpecial(int page1, int w1, int h1, int x1, int y1, int page2, int w2, int h2, int x2, int y2, int w3, int h3, int mode, ...) { + if (!w3 || !h3) + return; + + uint8 *table1 = 0; + uint8 *table2 = 0; + + if (mode == 2) { + va_list args; + va_start(args, mode); + table1 = va_arg(args, uint8 *); + table2 = va_arg(args, uint8 *); + va_end(args); + } + + int na = 0, nb = 0, nc = w3; + if (!calcBounds(w1, h1, x1, y1, w3, h3, na, nb, nc)) + return; + + int iu5_1 = na; + int iu6_1 = nb; + int ibw_1 = w3; + int dx_1 = x1; + int dy_1 = y1; + + if (!calcBounds(w2, h2, x2, y2, w3, h3, na, nb, nc)) + return; + + int iu5_2 = na; + int iu6_2 = nb; + int ibw_2 = w3; + int ibh_2 = h3; + int dx_2 = x2; + int dy_2 = y2; + + uint8 *src = getPagePtr(page1) + (dy_1 + iu6_2) * w1; + uint8 *dst = getPagePtr(page2) + (dy_2 + iu6_1) * w2; + + for (int i = 0; i < ibh_2; i++) { + uint8 *s = src + iu5_2 + dx_1; + uint8 *d = dst + iu5_1 + dx_2; + + if (mode == 0) { + memcpy(d, s, ibw_2); + + } else if (mode == 1) { + if (!(i & 1)) { + s++; + d++; + } + + for (int ii = (i & 1) ^ 1; ii < ibw_2; ii += 2) { + *d = *s; + d += 2; + s += 2; + } + + } else if (mode == 2) { + for (int ii = 0; ii < ibw_2; ii++) { + uint8 cmd = *s++; + uint8 offs = table1[cmd]; + if (!(offs & 0x80)) + cmd = table2[(offs << 8) | *d]; + *d++ = cmd; + } + + } else if (mode == 3) { + s = s - iu5_2 + ibw_1; + s = s - iu5_2 - 1; + for (int ii = 0; ii < ibw_2; ii++) + *d++ = *s--; + } + + dst += w2; + src += w1; + } + + if (!page2) + addDirtyRect(x2, y2, w2, h2); +} + +void Screen_LoL::copyBlockAndApplyOverlay(int page1, int x1, int y1, int page2, int x2, int y2, int w, int h, int dim, uint8 *ovl) { + if (!w || !h || !ovl) + return; + + const ScreenDim *cdim = getScreenDim(dim); + int ix = cdim->sx << 3; + int iy = cdim->sy; + int iw = cdim->w << 3; + int ih = cdim->h; + + int na = 0, nb = 0, nc = w; + if (!calcBounds(iw, ih, x2, y2, w, h, na, nb, nc)) + return; + + uint8 *src = getPagePtr(page1) + y1 * 320 + x1; + uint8 *dst = getPagePtr(page2) + (y2 + iy) * 320; + + for (int i = 0; i < h; i++) { + uint8 *s = src + na; + uint8 *d = dst + (x2 + ix); + + for (int ii = 0; ii < w; ii++) { + uint8 p = ovl[*s++]; + if (p) + *d = p; + d++; + } + + dst += 320; + src += 320; + } + + if (!page2) + addDirtyRect(x2 + ix, y2 + iy, w, h); +} + +void Screen_LoL::applyOverlaySpecial(int page1, int x1, int y1, int page2, int x2, int y2, int w, int h, int dim, int flag, uint8 *ovl) { + if (!w || !h || !ovl) + return; + + const ScreenDim *cdim = getScreenDim(dim); + int ix = cdim->sx << 3; + int iy = cdim->sy; + int iw = cdim->w << 3; + int ih = cdim->h; + + int na = 0, nb = 0, nc = w; + if (!calcBounds(iw, ih, x2, y2, w, h, na, nb, nc)) + return; + + uint8 *src = getPagePtr(page1) + y1 * 320 + x1; + uint8 *dst = getPagePtr(page2) + (y2 + iy) * 320; + + for (int i = 0; i < h; i++) { + uint8 *s = src + na; + uint8 *d = dst + (x2 + ix); + + if (flag) + d += (i >> 1); + + for (int ii = 0; ii < w; ii++) { + if (*s++) + *d = ovl[*d]; + d++; + } + + dst += 320; + src += 320; + } + + if (!page2) + addDirtyRect(x2 + ix, y2 + iy, w, h); +} + +void Screen_LoL::copyBlockAndApplyOverlayOutro(int srcPage, int dstPage, const uint8 *ovl) { + if (!ovl) + return; + + const byte *src = getCPagePtr(srcPage); + byte *dst = getPagePtr(dstPage); + + for (int y = 0; y < 200; ++y) { + for (int x = 0; x < 80; ++x) { + uint32 srcData = READ_LE_UINT32(src); src += 4; + uint32 dstData = READ_LE_UINT32(dst); + uint16 offset = 0; + + offset = ((srcData & 0xFF) << 8) + (dstData & 0xFF); + *dst++ = ovl[offset]; + + offset = (srcData & 0xFF00) + ((dstData & 0xFF00) >> 8); + *dst++ = ovl[offset]; + + srcData >>= 16; + dstData >>= 16; + + offset = ((srcData & 0xFF) << 8) + (dstData & 0xFF); + *dst++ = ovl[offset]; + + offset = (srcData & 0xFF00) + ((dstData & 0xFF00) >> 8); + *dst++ = ovl[offset]; + } + } +} + +void Screen_LoL::fadeToBlack(int delay, const UpdateFunctor *upFunc) { + Screen::fadeToBlack(delay, upFunc); + _fadeFlag = 2; +} + +void Screen_LoL::fadeToPalette1(int delay) { + loadSpecialColors(getPalette(1)); + fadePalette(getPalette(1), delay); + _fadeFlag = 0; +} + +void Screen_LoL::loadSpecialColors(Palette &dst) { + if (_use16ColorMode) + return; + + dst.copy(*_screenPalette, 192, 4); +} + +void Screen_LoL::copyColor(int dstColorIndex, int srcColorIndex) { + uint8 *s = _screenPalette->getData() + srcColorIndex * 3; + uint8 *d = _screenPalette->getData() + dstColorIndex * 3; + memcpy(d, s, 3); + + uint8 ci[3]; + ci[0] = (d[0] << 2) | (d[0] & 3); + ci[1] = (d[1] << 2) | (d[1] & 3); + ci[2] = (d[2] << 2) | (d[2] & 3); + + _system->getPaletteManager()->setPalette(ci, dstColorIndex, 1); +} + +bool Screen_LoL::fadeColor(int dstColorIndex, int srcColorIndex, uint32 elapsedTicks, uint32 totalTicks) { + if (_use16ColorMode) + return false; + + const uint8 *dst = _screenPalette->getData() + 3 * dstColorIndex; + const uint8 *src = _screenPalette->getData() + 3 * srcColorIndex; + uint8 *p = getPalette(1).getData() + 3 * dstColorIndex; + + bool res = false; + + int16 srcV = 0; + int16 dstV = 0; + int32 outV = 0; + + uint8 tmpPalEntry[3]; + + for (int i = 0; i < 3; i++) { + if (elapsedTicks < totalTicks) { + srcV = *src & 0x3F; + dstV = *dst & 0x3F; + + outV = srcV - dstV; + if (outV) + res = true; + + outV = dstV + ((((outV << 8) / (int32)totalTicks) * (int32)elapsedTicks) >> 8); + } else { + *p = outV = *src; + res = false; + } + + tmpPalEntry[i] = outV & 0xFF; + src++; + dst++; + p++; + } + + _internFadePalette->copy(*_screenPalette); + _internFadePalette->copy(tmpPalEntry, 0, 1, dstColorIndex); + setScreenPalette(*_internFadePalette); + updateScreen(); + + return res; +} + +Palette **Screen_LoL::generateFadeTable(Palette **dst, Palette *src1, Palette *src2, int numTabs) { + int len = _use16ColorMode ? 48 : 768; + if (!src1) + src1 = _screenPalette; + + uint8 *p1 = (*dst++)->getData(); + uint8 *p2 = src1->getData(); + uint8 *p3 = src2->getData(); + uint8 *p4 = p1; + uint8 *p5 = p2; + + for (int i = 0; i < len; i++) { + int8 val = (int8)*p3++ - (int8)*p2++; + *p4++ = (uint8)val; + } + + int16 t = 0; + int16 d = 256 / numTabs; + + for (int i = 1; i < numTabs - 1; i++) { + p2 = p5; + p3 = p1; + t += d; + p4 = (*dst++)->getData(); + + for (int ii = 0; ii < len; ii++) { + int16 val = (((int8)*p3++ * t) >> 8) + (int8)*p2++; + *p4++ = (uint8)val; + } + } + + memcpy(p1, p5, len); + (*dst)->copy(*src2); + + return ++dst; +} + +uint8 Screen_LoL::getShapePaletteSize(const uint8 *shp) { + return shp[10]; +} + +void Screen_LoL::mergeOverlay(int x, int y, int w, int h) { + // For now we convert to 16 colors on overlay merging. If that gives + // any problems, like Screen functionallity not prepared for the + // format PC98 16 color uses, we'll need to think of a better way. + // + // We must do this before merging the overlay, else the font colors + // will be wrong. + if (_use16ColorMode) + convertPC98Gfx(_sjisOverlayPtrs[0] + y * 640 + x, w, h, 640); + + Screen_v2::mergeOverlay(x, y, w, h); +} + +void Screen_LoL::convertPC98Gfx(uint8 *data, int w, int h, int pitch) { + while (h--) { + for (int i = 0; i < w; ++i) { + *data = (*data >> 4) & (*data & 0x0F); + ++data; + } + + data += pitch - w; + } +} + +void Screen_LoL::postProcessCursor(uint8 *data, int w, int h, int pitch) { + if (!_use16ColorMode) + return; + + while (h--) { + for (int i = 0; i < w; ++i) { + if (*data != _cursorColorKey) + *data = (*data >> 4) & (*data & 0x0F); + ++data; + } + + data += pitch - w; + } +} + +} // End of namespace Kyra + +#endif // ENABLE_LOL diff --git a/engines/kyra/graphics/screen_lol.h b/engines/kyra/graphics/screen_lol.h new file mode 100644 index 0000000000..ef14c52463 --- /dev/null +++ b/engines/kyra/graphics/screen_lol.h @@ -0,0 +1,105 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifdef ENABLE_LOL + +#ifndef KYRA_SCREEN_LOL_H +#define KYRA_SCREEN_LOL_H + +#include "kyra/graphics/screen_v2.h" + +namespace Kyra { + +class LoLEngine; + +class Screen_LoL : public Screen_v2 { +public: + Screen_LoL(LoLEngine *vm, OSystem *system); + ~Screen_LoL(); + + void fprintString(const char *format, int x, int y, uint8 col1, uint8 col2, uint flags, ...) GCC_PRINTF(2, 8); + void fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint flags, ...) GCC_PRINTF(2, 9); + + void drawShadedBox(int x1, int y1, int x2, int y2, int color1, int color2); + + void drawGridBox(int x, int y, int w, int h, int col); + void fadeClearSceneWindow(int delay); + + // smooth scrolling + void backupSceneWindow(int srcPageNum, int dstPageNum); + void restoreSceneWindow(int srcPageNum, int dstPageNum); + void clearGuiShapeMemory(int pageNum); + void copyGuiShapeFromSceneBackupBuffer(int srcPageNum, int dstPageNum); + void copyGuiShapeToSurface(int srcPageNum, int dstPageNum); + void smoothScrollZoomStepTop(int srcPageNum, int dstPageNum, int x, int y); + void smoothScrollZoomStepBottom(int srcPageNum, int dstPageNum, int x, int y); + void smoothScrollHorizontalStep(int pageNum, int x, int u2, int w); + void smoothScrollTurnStep1(int srcPage1Num, int srcPage2Num, int dstPageNum); + void smoothScrollTurnStep2(int srcPage1Num, int srcPage2Num, int dstPageNum); + void smoothScrollTurnStep3(int srcPage1Num, int srcPage2Num, int dstPageNum); + + void copyRegionSpecial(int page1, int w1, int h1, int x1, int y1, int page2, int w2, int h2, int x2, int y2, int w3, int h3, int mode, ...); + + // palette stuff + void fadeToBlack(int delay=0x54, const UpdateFunctor *upFunc = 0); + void fadeToPalette1(int delay); + void loadSpecialColors(Palette &dst); + void copyColor(int dstColorIndex, int srcColorIndex); + bool fadeColor(int dstColorIndex, int srcColorIndex, uint32 elapsedTicks, uint32 totalTicks); + Palette **generateFadeTable(Palette **dst, Palette *src1, Palette *src2, int numTabs); + + void generateGrayOverlay(const Palette &Pal, uint8 *grayOverlay, int factor, int addR, int addG, int addB, int lastColor, bool skipSpecialColors); + uint8 *getLevelOverlay(int index) { return _levelOverlays[index]; } + + void createTransparencyTablesIntern(const uint8 *ovl, int a, const uint8 *fxPal1, const uint8 *fxPal2, uint8 *outTable1, uint8 *outTable2, int b); + + void copyBlockAndApplyOverlay(int page1, int x1, int y1, int page2, int x2, int y2, int w, int h, int dim, uint8 *ovl); + void applyOverlaySpecial(int page1, int x1, int y1, int page2, int x2, int y2, int w, int h, int dim, int flag, uint8 *ovl); + + void copyBlockAndApplyOverlayOutro(int srcPage, int dstPage, const uint8 *ovl); + + uint8 getShapePaletteSize(const uint8 *shp); + + uint8 *_paletteOverlay1; + uint8 *_paletteOverlay2; + uint8 *_grayOverlay; + int _fadeFlag; + + // PC98 specific + static void convertPC98Gfx(uint8 *data, int w, int h, int pitch); + +private: + static const ScreenDim _screenDimTable256C[]; + static const ScreenDim _screenDimTable16C[]; + static const int _screenDimTableCount; + + uint8 *_levelOverlays[8]; + + void mergeOverlay(int x, int y, int w, int h); + void postProcessCursor(uint8 *data, int width, int height, int pitch); +}; + +} // End of namespace Kyra + +#endif + +#endif // ENABLE_LOL diff --git a/engines/kyra/graphics/screen_mr.cpp b/engines/kyra/graphics/screen_mr.cpp new file mode 100644 index 0000000000..acb99fe0b0 --- /dev/null +++ b/engines/kyra/graphics/screen_mr.cpp @@ -0,0 +1,130 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/screen_mr.h" +#include "kyra/engine/kyra_mr.h" + +namespace Kyra { + +Screen_MR::Screen_MR(KyraEngine_MR *vm, OSystem *system) + : Screen_v2(vm, system, _screenDimTable, _screenDimTableCount) { +} + +Screen_MR::~Screen_MR() { +} + +int Screen_MR::getLayer(int x, int y) { + if (x < 0) + x = 0; + else if (x >= 320) + x = 319; + if (y < 0) + y = 0; + else if (y >= 188) + y = 187; + + if (y < _maskMinY || y > _maskMaxY) + return 15; + + uint8 pixel = *(getCPagePtr(5) + y * 320 + x); + pixel &= 0x7F; + pixel >>= 3; + + if (pixel < 1) + pixel = 1; + else if (pixel > 15) + pixel = 15; + return pixel; +} + +byte Screen_MR::getShapeFlag1(int x, int y) { + if (y < _maskMinY || y > _maskMaxY) + return 0; + + uint8 color = _shapePages[0][y * SCREEN_W + x]; + color &= 0x80; + color ^= 0x80; + + if (color & 0x80) + return 1; + return 0; +} + +byte Screen_MR::getShapeFlag2(int x, int y) { + if (y < _maskMinY || y > _maskMaxY) + return 0; + + uint8 color = _shapePages[0][y * SCREEN_W + x]; + color &= 0x7F; + color &= 0x87; + return color; +} + +int Screen_MR::getDrawLayer(int x, int y) { + int xpos = x - 8; + int ypos = y; + int layer = 1; + + for (int curX = xpos; curX < xpos + 24; ++curX) { + int tempLayer = getShapeFlag2(curX, ypos); + + if (layer < tempLayer) + layer = tempLayer; + + if (layer >= 7) + return 7; + } + return layer; +} + +int Screen_MR::getDrawLayer2(int x, int y, int height) { + int xpos = x - 8; + int ypos = y; + int layer = 1; + + for (int useX = xpos; useX < xpos + 24; ++useX) { + for (int useY = ypos - height; useY < ypos; ++useY) { + int tempLayer = getShapeFlag2(useX, useY); + + if (tempLayer > layer) + layer = tempLayer; + + if (tempLayer >= 7) + return 7; + } + } + return layer; +} + +void Screen_MR::drawFilledBox(int x1, int y1, int x2, int y2, uint8 c1, uint8 c2, uint8 c3) { + fillRect(x1, y1, x2, y2, c1); + + fillRect(x1, y1, x2, y1+1, c2); + fillRect(x2-1, y1, x2, y2, c2); + + drawClippedLine(x1, y1, x1, y2, c3); + drawClippedLine(x1+1, y1+1, x1+1, y2-2, c3); + drawClippedLine(x1, y2, x2, y2, c3); + drawClippedLine(x1, y2-1, x2-1, y2-1, c3); +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen_mr.h b/engines/kyra/graphics/screen_mr.h new file mode 100644 index 0000000000..fcbef380c7 --- /dev/null +++ b/engines/kyra/graphics/screen_mr.h @@ -0,0 +1,53 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_MR_H +#define KYRA_SCREEN_MR_H + +#include "kyra/graphics/screen_v2.h" + +namespace Kyra { + +class KyraEngine_MR; + +class Screen_MR : public Screen_v2 { +public: + Screen_MR(KyraEngine_MR *vm, OSystem *system); + ~Screen_MR(); + + int getLayer(int x, int y); + + byte getShapeFlag1(int x, int y); + byte getShapeFlag2(int x, int y); + + int getDrawLayer(int x, int y); + int getDrawLayer2(int x, int y, int height); + + void drawFilledBox(int x1, int y1, int x2, int y2, uint8 c1, uint8 c2, uint8 c3); +private: + static const ScreenDim _screenDimTable[]; + static const int _screenDimTableCount; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/screen_v2.cpp b/engines/kyra/graphics/screen_v2.cpp new file mode 100644 index 0000000000..cb9fbca1b7 --- /dev/null +++ b/engines/kyra/graphics/screen_v2.cpp @@ -0,0 +1,461 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/screen_v2.h" + +#include "common/endian.h" + +namespace Kyra { + +Screen_v2::Screen_v2(KyraEngine_v1 *vm, OSystem *system, const ScreenDim *dimTable, const int dimTableSize) + : Screen(vm, system, dimTable, dimTableSize), _wsaFrameAnimBuffer(0) { + _wsaFrameAnimBuffer = new uint8[1024]; + assert(_wsaFrameAnimBuffer); +} + +Screen_v2::~Screen_v2() { + delete[] _wsaFrameAnimBuffer; +} + +uint8 *Screen_v2::generateOverlay(const Palette &pal, uint8 *buffer, int opColor, uint weight, int maxColor) { + if (!buffer) + return buffer; + + weight = MIN(weight, 255) >> 1; + + const byte opR = pal[opColor * 3 + 0]; + const byte opG = pal[opColor * 3 + 1]; + const byte opB = pal[opColor * 3 + 2]; + + uint8 *dst = buffer; + *dst++ = 0; + + int maxIndex = maxColor; + if (maxIndex == -1) { + if (_vm->game() == GI_LOL) { + if (_use16ColorMode) + maxIndex = 255; + else + maxIndex = 127; + } else { + maxIndex = 255; + } + } + + for (int i = 1; i != 256; ++i) { + const byte curR = pal[i * 3 + 0] - (((pal[i * 3 + 0] - opR) * weight) >> 7); + const byte curG = pal[i * 3 + 1] - (((pal[i * 3 + 1] - opG) * weight) >> 7); + const byte curB = pal[i * 3 + 2] - (((pal[i * 3 + 2] - opB) * weight) >> 7); + + uint16 idxSum = _use16ColorMode ? 0xFFFF : 0x7FFF; + byte index = opColor; + + for (int curIdx = 1; curIdx <= maxIndex; ++curIdx) { + if (!_use16ColorMode && i == curIdx) + continue; + + int diff = 0; + uint16 sum = 0; + + diff = pal[curIdx * 3 + 0] - curR; + sum += diff * diff; + diff = pal[curIdx * 3 + 1] - curG; + sum += diff * diff; + diff = pal[curIdx * 3 + 2] - curB; + sum += diff * diff; + + if (!sum) { + index = curIdx; + break; + } + + if (sum <= idxSum) { + if (!_use16ColorMode || (curIdx == opColor || curIdx != i)) { + idxSum = sum; + index = curIdx; + } + } + } + + *dst++ = index; + } + + return buffer; +} + +void Screen_v2::applyOverlay(int x, int y, int w, int h, int pageNum, const uint8 *overlay) { + if (pageNum == 0 || pageNum == 1) + addDirtyRect(x, y, w, h); + + uint8 *dst = getPagePtr(pageNum) + y * 320 + x; + while (h--) { + for (int wi = 0; wi < w; ++wi) { + uint8 index = *dst; + *dst++ = overlay[index]; + } + dst += 320 - w; + } +} + +int Screen_v2::findLeastDifferentColor(const uint8 *paletteEntry, const Palette &pal, uint8 firstColor, uint16 numColors, bool skipSpecialColors) { + int m = 0x7FFF; + int r = 0x101; + + for (int i = 0; i < numColors; i++) { + if (skipSpecialColors && i >= 0xC0 && i <= 0xC3) + continue; + + int v = paletteEntry[0] - pal[(i + firstColor) * 3 + 0]; + int c = v * v; + v = paletteEntry[1] - pal[(i + firstColor) * 3 + 1]; + c += (v * v); + v = paletteEntry[2] - pal[(i + firstColor) * 3 + 2]; + c += (v * v); + + if (c <= m) { + m = c; + r = i; + } + } + + return r; +} + +void Screen_v2::getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff) { + int maxDiff = 0; + diff = 0; + for (int i = 0; i < pal.getNumColors() * 3; ++i) { + diff = ABS(pal[i] - (*_screenPalette)[i]); + maxDiff = MAX(maxDiff, diff); + } + + delayInc = delay << 8; + if (maxDiff != 0) { + delayInc /= maxDiff; + delayInc = MIN(delayInc, 0x7FFF); + } + + delay = delayInc; + for (diff = 1; diff <= maxDiff; ++diff) { + if (delayInc >= 256) + break; + delayInc += delay; + } +} + +bool Screen_v2::timedPaletteFadeStep(uint8 *pal1, uint8 *pal2, uint32 elapsedTime, uint32 totalTime) { + Palette &p1 = getPalette(1); + + bool res = false; + for (int i = 0; i < p1.getNumColors() * 3; i++) { + uint8 out = 0; + + if (elapsedTime < totalTime) { + int32 d = ((pal2[i] & 0x3F) - (pal1[i] & 0x3F)); + if (d) + res = true; + + int32 val = ((((d << 8) / (int32)totalTime) * (int32)elapsedTime) >> 8); + out = ((pal1[i] & 0x3F) + (int8)val); + } else { + out = p1[i] = (pal2[i] & 0x3F); + res = false; + } + + (*_internFadePalette)[i] = out; + } + + setScreenPalette(*_internFadePalette); + updateScreen(); + + return res; +} + +const uint8 *Screen_v2::getPtrToShape(const uint8 *shpFile, int shape) { + uint16 shapes = READ_LE_UINT16(shpFile); + + if (shapes <= shape) + return 0; + + uint32 offset = READ_LE_UINT32(shpFile + (shape << 2) + 2); + + return shpFile + offset + 2; +} + +uint8 *Screen_v2::getPtrToShape(uint8 *shpFile, int shape) { + uint16 shapes = READ_LE_UINT16(shpFile); + + if (shapes <= shape) + return 0; + + uint32 offset = READ_LE_UINT32(shpFile + (shape << 2) + 2); + + return shpFile + offset + 2; +} + +int Screen_v2::getShapeScaledWidth(const uint8 *shpFile, int scale) { + if (!shpFile) + return 0; + int width = READ_LE_UINT16(shpFile + 3); + return (width * scale) >> 8; +} + +int Screen_v2::getShapeScaledHeight(const uint8 *shpFile, int scale) { + if (!shpFile) + return 0; + int height = shpFile[2]; + return (height * scale) >> 8; +} + +uint16 Screen_v2::getShapeSize(const uint8 *shp) { + if (!shp) + return 0; + return READ_LE_UINT16(shp + 6); +} + +uint8 *Screen_v2::makeShapeCopy(const uint8 *src, int index) { + const uint8 *shape = getPtrToShape(src, index); + if (!shape) + return 0; + + int size = getShapeSize(shape); + + uint8 *copy = new uint8[size]; + assert(copy); + memcpy(copy, shape, size); + + return copy; +} + +int Screen_v2::getLayer(int x, int y) { + if (x < 0) + x = 0; + else if (x >= 320) + x = 319; + if (y < 0) + y = 0; + else if (y >= 144) + y = 143; + + uint8 pixel = *(getCPagePtr(5) + y * 320 + x); + pixel &= 0x7F; + pixel >>= 3; + + if (pixel < 1) + pixel = 1; + else if (pixel > 15) + pixel = 15; + return pixel; +} + +int Screen_v2::getRectSize(int w, int h) { + if (w > 320 || h > 200) + return 0; + return w * h; +} + +void Screen_v2::setTextColorMap(const uint8 *cmap) { + setTextColor(cmap, 0, 15); +} + +void Screen_v2::wsaFrameAnimationStep(int x1, int y1, int x2, int y2, + int w1, int h1, int w2, int h2, int srcPage, int dstPage, int dim) { + + if (!w1 || !h1 || !w2 || !h2) + return; + + ScreenDim cdm = *getScreenDim(dim); + cdm.sx <<= 3; + cdm.w <<= 3; + + int na = 0, nb = 0, nc = w2; + + if (!calcBounds(cdm.w, cdm.h, x2, y2, w2, h2, na, nb, nc)) + return; + + const uint8 *src = getPagePtr(srcPage) + y1 * 320; + uint8 *dst = getPagePtr(dstPage) + (y2 + cdm.sy) * 320; + + int u = -1; + + do { + int t = (nb * h1) / h2; + if (t != u) { + u = t; + const uint8 *s = src + x1 + t * 320; + uint8 *dt = (uint8 *)_wsaFrameAnimBuffer; + + t = w2 - w1; + if (!t) { + memcpy(dt, s, w2); + } else if (t > 0) { + if (w1 == 1) { + memset(dt, *s, w2); + } else { + t = ((((((w2 - w1 + 1) & 0xFFFF) << 8) / w1) + 0x100) & 0xFFFF) << 8; + int bp = 0; + for (int i = 0; i < w1; i++) { + int cnt = (t >> 16); + bp += (t & 0xFFFF); + if (bp > 0xFFFF) { + bp -= 0xFFFF; + cnt++; + } + memset(dt, *s++, cnt); + dt += cnt; + } + } + } else { + if (w2 == 1) { + *dt = *s; + } else { + t = (((((w1 - w2) & 0xFFFF) << 8) / w2) & 0xFFFF) << 8; + int bp = 0; + for (int i = 0; i < w2; i++) { + *dt++ = *s++; + bp += (t & 0xFFFF); + if (bp > 0xFFFF) { + bp -= 0xFFFF; + s++; + } + s += (t >> 16); + } + } + } + } + memcpy(dst + x2 + cdm.sx, _wsaFrameAnimBuffer + na, w2); + dst += 320; + } while (++nb < h2); + + if (!dstPage) + addDirtyRect(x2, y2, w2, h2); +} + +void Screen_v2::copyPageMemory(int srcPage, int srcPos, int dstPage, int dstPos, int numBytes) { + const uint8 *src = getPagePtr(srcPage) + srcPos; + uint8 *dst = getPagePtr(dstPage) + dstPos; + memcpy(dst, src, numBytes); +} + +void Screen_v2::copyRegionEx(int srcPage, int srcW, int srcH, int dstPage, int dstX, int dstY, int dstW, int dstH, const ScreenDim *dim, bool flag) { + int x0 = dim->sx << 3; + int y0 = dim->sy; + int w0 = dim->w << 3; + int h0 = dim->h; + + int x1 = dstX; + int y1 = dstY; + int w1 = dstW; + int h1 = dstH; + + int x2, y2, w2; + + calcBounds(w0, h0, x1, y1, w1, h1, x2, y2, w2); + + const uint8 *src = getPagePtr(srcPage) + (320 * srcH) + srcW; + uint8 *dst = getPagePtr(dstPage) + 320 * (y0 + y1); + + for (int y = 0; y < h1; y++) { + const uint8 *s = src + x2; + uint8 *d = dst + x0 + x1; + + if (flag) + d += (h1 >> 1); + + for (int x = 0; x < w1; x++) { + if (*s) + *d = *s; + s++; + d++; + } + dst += 320; + src += 320; + } +} + +bool Screen_v2::calcBounds(int w0, int h0, int &x1, int &y1, int &w1, int &h1, int &x2, int &y2, int &w2) { + x2 = 0; + y2 = 0; + w2 = w1; + + int t = x1 + w1; + if (t < 1) { + w1 = h1 = -1; + } else { + if (t <= x1) { + x2 = w1 - t; + w1 = t; + x1 = 0; + } + t = w0 - x1; + if (t < 1) { + w1 = h1 = -1; + } else { + if (t <= w1) { + w1 = t; + } + w2 -= w1; + t = h1 + y1; + if (t < 1) { + w1 = h1 = -1; + } else { + if (t <= y1) { + y2 = h1 - t; + h1 = t; + y1 = 0; + } + t = h0 - y1; + if (t < 1) { + w1 = h1 = -1; + } else { + if (t <= h1) { + h1 = t; + } + } + } + } + } + + return w1 != -1; +} + +void Screen_v2::checkedPageUpdate(int srcPage, int dstPage) { + const uint32 *src = (const uint32 *)getPagePtr(srcPage); + uint32 *dst = (uint32 *)getPagePtr(dstPage); + uint32 *page0 = (uint32 *)getPagePtr(0); + + bool updated = false; + + for (int y = 0; y < 200; ++y) { + for (int x = 0; x < 80; ++x, ++src, ++dst, ++page0) { + if (*src != *dst) { + updated = true; + *dst = *page0 = *src; + } + } + } + + if (updated) + addDirtyRect(0, 0, 320, 200); +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/screen_v2.h b/engines/kyra/graphics/screen_v2.h new file mode 100644 index 0000000000..9c8aa12563 --- /dev/null +++ b/engines/kyra/graphics/screen_v2.h @@ -0,0 +1,81 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_SCREEN_V2_H +#define KYRA_SCREEN_V2_H + +#include "kyra/graphics/screen.h" +#include "kyra/engine/kyra_v2.h" + +namespace Kyra { + +class Screen_v2 : public Screen { +public: + Screen_v2(KyraEngine_v1 *vm, OSystem *system, const ScreenDim *dimTable, const int dimTableSize); + ~Screen_v2(); + + // screen page handling + void checkedPageUpdate(int srcPage, int dstPage); + + // palette handling + uint8 *generateOverlay(const Palette &pal, uint8 *buffer, int color, uint weight, int maxColor = -1); + void applyOverlay(int x, int y, int w, int h, int pageNum, const uint8 *overlay); + int findLeastDifferentColor(const uint8 *paletteEntry, const Palette &pal, uint8 firstColor, uint16 numColors, bool skipSpecialColors = false); + + virtual void getFadeParams(const Palette &pal, int delay, int &delayInc, int &diff); + + bool timedPaletteFadeStep(uint8 *pal1, uint8 *pal2, uint32 elapsedTime, uint32 totalTime); + + // shape handling + uint8 *getPtrToShape(uint8 *shpFile, int shape); + const uint8 *getPtrToShape(const uint8 *shpFile, int shape); + + int getShapeScaledWidth(const uint8 *shpFile, int scale); + int getShapeScaledHeight(const uint8 *shpFile, int scale); + + uint16 getShapeSize(const uint8 *shp); + + uint8 *makeShapeCopy(const uint8 *src, int index); + + // rect handling + int getRectSize(int w, int h); + bool calcBounds(int w0, int h0, int &x1, int &y1, int &w1, int &h1, int &x2, int &y2, int &w2); + + // text display + void setTextColorMap(const uint8 *cmap); + + // layer handling + virtual int getLayer(int x, int y); + + // special WSA handling + void wsaFrameAnimationStep(int x1, int y1, int x2, int y2, int w1, int h1, int w2, int h2, int srcPage, int dstPage, int dim); + + // used in non-interactive HoF/LoL demos + void copyPageMemory(int srcPage, int srcPos, int dstPage, int dstPos, int numBytes); + void copyRegionEx(int srcPage, int srcW, int srcH, int dstPage, int dstX,int dstY, int dstW, int dstH, const ScreenDim *d, bool flag = false); +protected: + uint8 *_wsaFrameAnimBuffer; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/vqa.cpp b/engines/kyra/graphics/vqa.cpp new file mode 100644 index 0000000000..5a4e250b42 --- /dev/null +++ b/engines/kyra/graphics/vqa.cpp @@ -0,0 +1,667 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +// Player for Kyrandia 3 VQA movies, based on the information found at +// http://multimedia.cx/VQA_INFO.TXT +// +// The benchl.vqa movie (or whatever it is) is not supported. It does not have +// a FINF chunk. +// +// The jung2.vqa movie does work, but only thanks to a grotesque hack. + +#include "kyra/kyra_v1.h" +#include "kyra/graphics/vqa.h" +#include "kyra/graphics/screen.h" + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + +#include "common/system.h" +#include "common/events.h" + +#include "graphics/palette.h" +#include "graphics/surface.h" + +namespace Kyra { + +static uint32 readTag(Common::SeekableReadStream *stream) { + // Some tags have to be on an even offset, so they are padded with a + // zero byte. Skip that. + + uint32 tag = stream->readUint32BE(); + + if (stream->eos()) + return 0; + + if (!(tag & 0xFF000000)) { + tag = (tag << 8) | stream->readByte(); + } + + return tag; +} + +VQADecoder::VQADecoder() { + memset(&_header, 0, sizeof(_header)); +} + +VQADecoder::~VQADecoder() { + close(); + delete[] _frameInfo; +} + +bool VQADecoder::loadStream(Common::SeekableReadStream *stream) { + close(); + _fileStream = stream; + + if (_fileStream->readUint32BE() != MKTAG('F','O','R','M')) { + warning("VQADecoder::loadStream(): Cannot find `FORM' tag"); + return false; + } + + // Ignore the size of the FORM chunk. We're only interested in its + // children. + _fileStream->readUint32BE(); + + if (_fileStream->readUint32BE() != MKTAG('W','V','Q','A')) { + warning("VQADecoder::loadStream(): Cannot find `WVQA' tag"); + return false; + } + + // We want to find both a VQHD chunk containing the header, and a FINF + // chunk containing the frame offsets. + + bool foundVQHD = false; + bool foundFINF = false; + + VQAAudioTrack *audioTrack = NULL; + + // The information we need is stored in two chunks: VQHD and FINF. We + // need both of them before we can begin decoding the movie. + + while (!foundVQHD || !foundFINF) { + uint32 tag = readTag(stream); + uint32 size = _fileStream->readUint32BE(); + + switch (tag) { + case MKTAG('V','Q','H','D'): + handleVQHD(_fileStream); + if (_header.flags & 1) { + audioTrack = new VQAAudioTrack(&_header, getSoundType()); + addTrack(audioTrack); + } + foundVQHD = true; + break; + case MKTAG('F','I','N','F'): + if (!foundVQHD) { + warning("VQADecoder::loadStream(): Found `FINF' before `VQHD'"); + return false; + } + if (size != 4 * getFrameCount()) { + warning("VQADecoder::loadStream(): Expected size %d for `FINF' chunk, but got %u", 4 * getFrameCount(), size); + return false; + } + handleFINF(_fileStream); + foundFINF = true; + break; + default: + warning("VQADecoder::loadStream(): Unknown tag `%s'", tag2str(tag)); + _fileStream->seek(size, SEEK_CUR); + break; + } + } + + return true; +} + +void VQADecoder::handleVQHD(Common::SeekableReadStream *stream) { + _header.version = stream->readUint16LE(); + _header.flags = stream->readUint16LE(); + _header.numFrames = stream->readUint16LE(); + _header.width = stream->readUint16LE(); + _header.height = stream->readUint16LE(); + _header.blockW = stream->readByte(); + _header.blockH = stream->readByte(); + _header.frameRate = stream->readByte(); + _header.cbParts = stream->readByte(); + _header.colors = stream->readUint16LE(); + _header.maxBlocks = stream->readUint16LE(); + _header.unk1 = stream->readUint32LE(); + _header.unk2 = stream->readUint16LE(); + _header.freq = stream->readUint16LE(); + _header.channels = stream->readByte(); + _header.bits = stream->readByte(); + _header.unk3 = stream->readUint32LE(); + _header.unk4 = stream->readUint16LE(); + _header.maxCBFZSize = stream->readUint32LE(); + _header.unk5 = stream->readUint32LE(); + + _frameInfo = new uint32[_header.numFrames + 1]; + + VQAVideoTrack *videoTrack = new VQAVideoTrack(&_header); + addTrack(videoTrack); + + // Kyrandia 3 uses version 1 VQA files, and is the only known game to + // do so. This version of the format has some implicit default values. + + if (_header.version == 1) { + if (_header.freq == 0) + _header.freq = 22050; + if (_header.channels == 0) + _header.channels = 1; + if (_header.bits == 0) + _header.bits = 8; + } + + if (_header.flags & 1) { + // Kyrandia 3 uses 8-bit sound, and so far testing indicates + // that it's all mono. + // + // This is good, because it means we won't have to worry about + // the confusing parts of the VQA spec, where 8- and 16-bit + // data have different signedness and stereo sample layout + // varies between different games. + + assert(_header.bits == 8); + assert(_header.channels == 1); + } +} + +void VQADecoder::handleFINF(Common::SeekableReadStream *stream) { + for (int i = 0; i < _header.numFrames; i++) { + _frameInfo[i] = 2 * stream->readUint32LE(); + } + + // HACK: This flag is set in jung2.vqa, and its purpose - if it has + // one - is currently unknown. It can't be a general purpose flag, + // because in large movies the frame offset can be large enough to + // set this flag, though of course never for the first frame. + // + // At least in my copy of Kyrandia 3, _frameInfo[0] is 0x81000098, and + // the desired index is 0x4716. So the value should be 0x80004716, but + // I don't want to hard-code it. Instead, scan the file for the offset + // to the first VQFR chunk. + + if (_frameInfo[0] & 0x01000000) { + uint32 oldPos = stream->pos(); + + while (1) { + uint32 scanTag = readTag(stream); + uint32 scanSize = stream->readUint32BE(); + + if (stream->eos()) + break; + + if (scanTag == MKTAG('V','Q','F','R')) { + _frameInfo[0] = (stream->pos() - 8) | 0x80000000; + break; + } + + stream->seek(scanSize, SEEK_CUR); + } + + stream->seek(oldPos); + } + + _frameInfo[_header.numFrames] = 0x7FFFFFFF; +} + +void VQADecoder::readNextPacket() { + VQAVideoTrack *videoTrack = (VQAVideoTrack *)getTrack(0); + VQAAudioTrack *audioTrack = (VQAAudioTrack *)getTrack(1); + + assert(videoTrack); + + int curFrame = videoTrack->getCurFrame(); + + // Stop if reading the tag is enough to put us ahead of the next frame + int32 end = (_frameInfo[curFrame + 1] & 0x7FFFFFFF) - 7; + + // At this point, we probably only need to adjust for the offset in the + // stream to be even. But we may as well do this to really make sure + // we have the correct offset. + if (curFrame >= 0) { + _fileStream->seek(_frameInfo[curFrame] & 0x7FFFFFFF); + if (_frameInfo[curFrame] & 0x80000000) { + videoTrack->setHasDirtyPalette(); + } + } + + while (!_fileStream->eos() && _fileStream->pos() < end) { + uint32 tag = readTag(_fileStream); + uint32 size; + + switch (tag) { + case MKTAG('S','N','D','0'): // Uncompressed sound + assert(audioTrack); + audioTrack->handleSND0(_fileStream); + break; + case MKTAG('S','N','D','1'): // Compressed sound, almost like AUD + assert(audioTrack); + audioTrack->handleSND1(_fileStream); + break; + case MKTAG('S','N','D','2'): // Compressed sound + assert(audioTrack); + audioTrack->handleSND2(_fileStream); + break; + case MKTAG('V','Q','F','R'): + videoTrack->handleVQFR(_fileStream); + break; + case MKTAG('C','M','D','S'): + // The purpose of this is unknown, but it's known to + // exist so don't warn about it. + size = _fileStream->readUint32BE(); + _fileStream->seek(size, SEEK_CUR); + break; + default: + warning("VQADecoder::readNextPacket(): Unknown tag `%s'", tag2str(tag)); + size = _fileStream->readUint32BE(); + _fileStream->seek(size, SEEK_CUR); + break; + } + } +} + +// ----------------------------------------------------------------------- + +VQADecoder::VQAAudioTrack::VQAAudioTrack(const VQAHeader *header, Audio::Mixer::SoundType soundType) : + AudioTrack(soundType) { + _audioStream = Audio::makeQueuingAudioStream(header->freq, false); +} + +VQADecoder::VQAAudioTrack::~VQAAudioTrack() { + delete _audioStream; +} + +Audio::AudioStream *VQADecoder::VQAAudioTrack::getAudioStream() const { + return _audioStream; +} + +void VQADecoder::VQAAudioTrack::handleSND0(Common::SeekableReadStream *stream) { + uint32 size = stream->readUint32BE(); + byte *buf = (byte *)malloc(size); + stream->read(buf, size); + _audioStream->queueBuffer(buf, size, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); +} + +void VQADecoder::VQAAudioTrack::handleSND1(Common::SeekableReadStream *stream) { + stream->readUint32BE(); + uint16 outsize = stream->readUint16LE(); + uint16 insize = stream->readUint16LE(); + byte *inbuf = (byte *)malloc(insize); + + stream->read(inbuf, insize); + + if (insize == outsize) { + _audioStream->queueBuffer(inbuf, insize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); + } else { + const int8 WSTable2Bit[] = { -2, -1, 0, 1 }; + const int8 WSTable4Bit[] = { + -9, -8, -6, -5, -4, -3, -2, -1, + 0, 1, 2, 3, 4, 5, 6, 8 + }; + + byte *outbuf = (byte *)malloc(outsize); + byte *in = inbuf; + byte *out = outbuf; + int16 curSample = 0x80; + uint16 bytesLeft = outsize; + + while (bytesLeft > 0) { + uint16 input = *in++ << 2; + byte code = (input >> 8) & 0xFF; + int8 count = (input & 0xFF) >> 2; + int i; + + switch (code) { + case 2: + if (count & 0x20) { + /* NOTE: count is signed! */ + count <<= 3; + curSample += (count >> 3); + *out++ = curSample; + bytesLeft--; + } else { + for (; count >= 0; count--) { + *out++ = *in++; + bytesLeft--; + } + curSample = *(out - 1); + } + break; + case 1: + for (; count >= 0; count--) { + code = *in++; + + for (i = 0; i < 2; i++) { + curSample += WSTable4Bit[code & 0x0F]; + curSample = CLIP(curSample, 0, 255); + code >>= 4; + *out++ = curSample; + } + + bytesLeft -= 2; + } + break; + case 0: + for (; count >= 0; count--) { + code = *in++; + + for (i = 0; i < 4; i++) { + curSample += WSTable2Bit[code & 0x03]; + curSample = CLIP(curSample, 0, 255); + code >>= 2; + *out++ = curSample; + } + + bytesLeft -= 4; + } + break; + default: + for (; count >= 0; count--) { + *out++ = curSample; + bytesLeft--; + } + break; + } + } + _audioStream->queueBuffer(outbuf, outsize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); + free(inbuf); + } +} + +void VQADecoder::VQAAudioTrack::handleSND2(Common::SeekableReadStream *stream) { + uint32 size = stream->readUint32BE(); + warning("VQADecoder::VQAAudioTrack::handleSND2(): `SND2' is not implemented"); + stream->seek(size, SEEK_CUR); +} + +// ----------------------------------------------------------------------- + +VQADecoder::VQAVideoTrack::VQAVideoTrack(const VQAHeader *header) { + memset(_palette, 0, sizeof(_palette)); + _dirtyPalette = false; + + _width = header->width; + _height = header->height; + _blockW = header->blockW; + _blockH = header->blockH; + _cbParts = header->cbParts; + + _newFrame = false; + + _curFrame = -1; + _frameCount = header->numFrames; + _frameRate = header->frameRate; + + _codeBookSize = 0xF00 * header->blockW * header->blockH; + _compressedCodeBook = false; + _codeBook = new byte[_codeBookSize]; + _partialCodeBookSize = 0; + _numPartialCodeBooks = 0; + _partialCodeBook = new byte[_codeBookSize]; + _numVectorPointers = (header->width / header->blockW) * (header->height * header->blockH); + _vectorPointers = new uint16[_numVectorPointers]; + + memset(_codeBook, 0, _codeBookSize); + memset(_partialCodeBook, 0, _codeBookSize); + memset(_vectorPointers, 0, _numVectorPointers); + + _surface = new Graphics::Surface(); + _surface->create(header->width, header->height, Graphics::PixelFormat::createFormatCLUT8()); +} + +VQADecoder::VQAVideoTrack::~VQAVideoTrack() { + _surface->free(); + delete _surface; + delete[] _codeBook; + delete[] _partialCodeBook; + delete[] _vectorPointers; +} + +uint16 VQADecoder::VQAVideoTrack::getWidth() const { + return _width; +} + +uint16 VQADecoder::VQAVideoTrack::getHeight() const { + return _height; +} + +Graphics::PixelFormat VQADecoder::VQAVideoTrack::getPixelFormat() const { + return _surface->format; +} + +int VQADecoder::VQAVideoTrack::getCurFrame() const { + return _curFrame; +} + +int VQADecoder::VQAVideoTrack::getFrameCount() const { + return _frameCount; +} + +Common::Rational VQADecoder::VQAVideoTrack::getFrameRate() const { + return _frameRate; +} + +void VQADecoder::VQAVideoTrack::setHasDirtyPalette() { + _dirtyPalette = true; +} + +bool VQADecoder::VQAVideoTrack::hasDirtyPalette() const { + return _dirtyPalette; +} + +const byte *VQADecoder::VQAVideoTrack::getPalette() const { + _dirtyPalette = false; + return _palette; +} + +const Graphics::Surface *VQADecoder::VQAVideoTrack::decodeNextFrame() { + if (_newFrame) { + _newFrame = false; + + int blockPitch = _width / _blockW; + + for (int by = 0; by < _height / _blockH; by++) { + for (int bx = 0; bx < blockPitch; bx++) { + byte *dst = (byte *)_surface->getBasePtr(bx * _blockW, by * _blockH); + int val = _vectorPointers[by * blockPitch + bx]; + int i; + + if ((val & 0xFF00) == 0xFF00) { + // Solid color + for (i = 0; i < _blockH; i++) { + memset(dst, 255 - (val & 0xFF), _blockW); + dst += _width; + } + } else { + // Copy data from _vectorPointers. I'm not sure + // why we don't use the three least significant + // bits of 'val'. + byte *src = &_codeBook[(val >> 3) * _blockW * _blockH]; + + for (i = 0; i < _blockH; i++) { + memcpy(dst, src, _blockW); + src += _blockW; + dst += _width; + } + } + } + } + + if (_numPartialCodeBooks == _cbParts) { + if (_compressedCodeBook) { + Screen::decodeFrame4(_partialCodeBook, _codeBook, _codeBookSize); + } else { + memcpy(_codeBook, _partialCodeBook, _partialCodeBookSize); + } + _numPartialCodeBooks = 0; + _partialCodeBookSize = 0; + } + } + + _curFrame++; + return _surface; +} + +void VQADecoder::VQAVideoTrack::handleVQFR(Common::SeekableReadStream *stream) { + uint32 size = stream->readUint32BE(); + int32 end = stream->pos() + size - 8; + byte *inbuf; + + _newFrame = true; + + while (stream->pos() < end) { + uint32 tag = readTag(stream); + uint32 i; + size = stream->readUint32BE(); + + switch (tag) { + case MKTAG('C','B','F','0'): // Full codebook + stream->read(_codeBook, size); + break; + case MKTAG('C','B','F','Z'): // Full codebook + inbuf = (byte *)malloc(size); + stream->read(inbuf, size); + Screen::decodeFrame4(inbuf, _codeBook, _codeBookSize); + free(inbuf); + break; + case MKTAG('C','B','P','0'): // Partial codebook + _compressedCodeBook = false; + stream->read(_partialCodeBook + _partialCodeBookSize, size); + _partialCodeBookSize += size; + _numPartialCodeBooks++; + break; + case MKTAG('C','B','P','Z'): // Partial codebook + _compressedCodeBook = true; + stream->read(_partialCodeBook + _partialCodeBookSize, size); + _partialCodeBookSize += size; + _numPartialCodeBooks++; + break; + case MKTAG('C','P','L','0'): // Palette + assert(size <= 3 * 256); + stream->read(_palette, size); + break; + case MKTAG('C','P','L','Z'): // Palette + inbuf = (byte *)malloc(size); + stream->read(inbuf, size); + Screen::decodeFrame4(inbuf, _palette, 3 * 256); + free(inbuf); + break; + case MKTAG('V','P','T','0'): // Frame data + assert(size / 2 <= _numVectorPointers); + for (i = 0; i < size / 2; i++) + _vectorPointers[i] = stream->readUint16LE(); + break; + case MKTAG('V','P','T','Z'): // Frame data + inbuf = (byte *)malloc(size); + stream->read(inbuf, size); + size = Screen::decodeFrame4(inbuf, (uint8 *)_vectorPointers, 2 * _numVectorPointers); + for (i = 0; i < size / 2; i++) + _vectorPointers[i] = TO_LE_16(_vectorPointers[i]); + free(inbuf); + break; + default: + warning("VQADecoder::VQAVideoTrack::handleVQFR(): Unknown `VQFR' sub-tag `%s'", tag2str(tag)); + stream->seek(size, SEEK_CUR); + break; + } + } +} + +// ----------------------------------------------------------------------- + +VQAMovie::VQAMovie(KyraEngine_v1 *vm, OSystem *system) { + _system = system; + _vm = vm; + _screen = _vm->screen(); + _decoder = new VQADecoder(); +} + +VQAMovie::~VQAMovie() { + close(); + delete _decoder; +} + +bool VQAMovie::open(const char *filename) { + if (_file.open(filename)) { + return true; + } + return false; +} + +void VQAMovie::close() { + if (_file.isOpen()) { + _file.close(); + } +} + +void VQAMovie::play() { + if (_decoder->loadStream(&_file)) { + Common::EventManager *eventMan = _vm->getEventManager(); + int width = _decoder->getWidth(); + int height = _decoder->getHeight(); + int x = (Screen::SCREEN_W - width) / 2; + int y = (Screen::SCREEN_H - height) / 2; + + _decoder->start(); + + // Note that decoding starts at frame -1. That's because there + // is usually sound data before the first frame, probably to + // avoid sound underflow. + + while (!_decoder->endOfVideo()) { + Common::Event event; + while (eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode == Common::KEYCODE_ESCAPE) + return; + break; + case Common::EVENT_RTL: + case Common::EVENT_QUIT: + return; + default: + break; + } + } + + if (_decoder->needsUpdate()) { + const Graphics::Surface *surface = _decoder->decodeNextFrame(); + if (_decoder->hasDirtyPalette()) { + const byte *decoderPalette = _decoder->getPalette(); + byte systemPalette[256 * 3]; + for (int i = 0; i < ARRAYSIZE(systemPalette); i++) { + systemPalette[i] = (decoderPalette[i] * 0xFF) / 0x3F; + } + _system->getPaletteManager()->setPalette(systemPalette, 0, 256); + } + + _system->copyRectToScreen((const byte *)surface->getBasePtr(0, 0), surface->pitch, x, y, width, height); + } + + _system->updateScreen(); + _system->delayMillis(10); + } + } +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/vqa.h b/engines/kyra/graphics/vqa.h new file mode 100644 index 0000000000..df51a81988 --- /dev/null +++ b/engines/kyra/graphics/vqa.h @@ -0,0 +1,161 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_VQA_H +#define KYRA_VQA_H + +#include "video/video_decoder.h" +#include "common/file.h" +#include "common/rational.h" + +class OSystem; + +namespace Audio { +class QueuingAudioStream; +} // End of namespace Audio + +namespace Kyra { + +class KyraEngine_v1; +class Screen; + +class VQADecoder : public Video::VideoDecoder { +public: + VQADecoder(); + virtual ~VQADecoder(); + + bool loadStream(Common::SeekableReadStream *stream); + void readNextPacket(); + +private: + Common::SeekableReadStream *_fileStream; + + void handleVQHD(Common::SeekableReadStream *stream); + void handleFINF(Common::SeekableReadStream *stream); + + struct VQAHeader { + uint16 version; + uint16 flags; + uint16 numFrames; + uint16 width; + uint16 height; + uint8 blockW; + uint8 blockH; + uint8 frameRate; + uint8 cbParts; + uint16 colors; + uint16 maxBlocks; + uint32 unk1; + uint16 unk2; + uint16 freq; + uint8 channels; + uint8 bits; + uint32 unk3; + uint16 unk4; + uint32 maxCBFZSize; + uint32 unk5; + }; + + VQAHeader _header; + uint32 *_frameInfo; + + class VQAAudioTrack : public AudioTrack { + public: + VQAAudioTrack(const VQAHeader *header, Audio::Mixer::SoundType soundType); + ~VQAAudioTrack(); + + void handleSND0(Common::SeekableReadStream *stream); + void handleSND1(Common::SeekableReadStream *stream); + void handleSND2(Common::SeekableReadStream *stream); + + protected: + Audio::AudioStream *getAudioStream() const; + + private: + Audio::QueuingAudioStream *_audioStream; + }; + + class VQAVideoTrack : public FixedRateVideoTrack { + public: + VQAVideoTrack(const VQAHeader *header); + ~VQAVideoTrack(); + + uint16 getWidth() const; + uint16 getHeight() const; + Graphics::PixelFormat getPixelFormat() const; + int getCurFrame() const; + int getFrameCount() const; + const Graphics::Surface *decodeNextFrame(); + + void setHasDirtyPalette(); + bool hasDirtyPalette() const; + const byte *getPalette() const; + + void handleVQFR(Common::SeekableReadStream *stream); + + protected: + Common::Rational getFrameRate() const; + + private: + Graphics::Surface *_surface; + byte _palette[3 * 256]; + mutable bool _dirtyPalette; + + bool _newFrame; + + uint16 _width, _height; + uint8 _blockW, _blockH; + uint8 _cbParts; + int _frameCount; + int _curFrame; + byte _frameRate; + + uint32 _codeBookSize; + bool _compressedCodeBook; + byte *_codeBook; + int _partialCodeBookSize; + int _numPartialCodeBooks; + byte *_partialCodeBook; + uint32 _numVectorPointers; + uint16 *_vectorPointers; + }; +}; + +class VQAMovie { +public: + VQAMovie(KyraEngine_v1 *vm, OSystem *system); + ~VQAMovie(); + + bool open(const char *filename); + void close(); + void play(); +private: + OSystem *_system; + KyraEngine_v1 *_vm; + Screen *_screen; + VQADecoder *_decoder; + Common::File _file; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/graphics/wsamovie.cpp b/engines/kyra/graphics/wsamovie.cpp new file mode 100644 index 0000000000..6cbda7b6c9 --- /dev/null +++ b/engines/kyra/graphics/wsamovie.cpp @@ -0,0 +1,463 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "kyra/graphics/wsamovie.h" +#include "kyra/resource/resource.h" + +#include "common/endian.h" + +namespace Kyra { + +WSAMovie_v1::WSAMovie_v1(KyraEngine_v1 *vm) + : Movie(vm), _frameData(0), _frameOffsTable(0), _offscreenBuffer(0), _deltaBuffer(0) { +} + +WSAMovie_v1::~WSAMovie_v1() { + close(); +} + +int WSAMovie_v1::open(const char *filename, int offscreenDecode, Palette *palBuf) { + close(); + + uint32 flags = 0; + uint32 fileSize; + uint8 *p = _vm->resource()->fileData(filename, &fileSize); + if (!p) + return 0; + + const uint8 *wsaData = p; + _numFrames = READ_LE_UINT16(wsaData); wsaData += 2; + _width = READ_LE_UINT16(wsaData); wsaData += 2; + _height = READ_LE_UINT16(wsaData); wsaData += 2; + _deltaBufferSize = READ_LE_UINT16(wsaData); wsaData += 2; + _offscreenBuffer = NULL; + _flags = 0; + if (_vm->gameFlags().useAltShapeHeader) { + flags = READ_LE_UINT16(wsaData); + wsaData += 2; + } + + uint32 offsPal = 0; + if (flags & 1) { + offsPal = 0x300; + _flags |= WF_HAS_PALETTE; + if (palBuf) + _screen->loadPalette(wsaData + 8 + ((_numFrames << 2) & 0xFFFF), *palBuf, 0x300); + } + + if (offscreenDecode) { + _flags |= WF_OFFSCREEN_DECODE; + const int offscreenBufferSize = _width * _height; + _offscreenBuffer = new uint8[offscreenBufferSize]; + memset(_offscreenBuffer, 0, offscreenBufferSize); + } + + if (_numFrames & 0x8000) { + // This is used in the Amiga version. + if (_vm->gameFlags().platform != Common::kPlatformAmiga) + warning("Unhandled wsa flags 0x8000"); + _flags |= WF_FLIPPED; + _numFrames &= 0x7FFF; + } + _currentFrame = _numFrames; + + _deltaBuffer = new uint8[_deltaBufferSize]; + memset(_deltaBuffer, 0, _deltaBufferSize); + + // read frame offsets + _frameOffsTable = new uint32[_numFrames + 2]; + _frameOffsTable[0] = 0; + uint32 frameDataOffs = READ_LE_UINT32(wsaData); wsaData += 4; + bool firstFrame = true; + + if (frameDataOffs == 0) { + firstFrame = false; + frameDataOffs = READ_LE_UINT32(wsaData); + _flags |= WF_NO_FIRST_FRAME; + } + + for (int i = 1; i < _numFrames + 2; ++i) { + _frameOffsTable[i] = READ_LE_UINT32(wsaData); + if (_frameOffsTable[i]) + _frameOffsTable[i] -= frameDataOffs; + wsaData += 4; + } + + if (!_frameOffsTable[_numFrames + 1]) + _flags |= WF_NO_LAST_FRAME; + + // skip palette + wsaData += offsPal; + + // read frame data + const int frameDataSize = p + fileSize - wsaData; + _frameData = new uint8[frameDataSize]; + memcpy(_frameData, wsaData, frameDataSize); + + // decode first frame + if (firstFrame) + Screen::decodeFrame4(_frameData, _deltaBuffer, _deltaBufferSize); + + delete[] p; + _opened = true; + + return _numFrames; +} + +void WSAMovie_v1::close() { + if (_opened) { + delete[] _deltaBuffer; + delete[] _offscreenBuffer; + delete[] _frameOffsTable; + delete[] _frameData; + _opened = false; + } +} + +void WSAMovie_v1::displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2) { + if (frameNum >= _numFrames || frameNum < 0 || !_opened) + return; + + _x = x; + _y = y; + _drawPage = pageNum; + + uint8 *dst = 0; + if (_flags & WF_OFFSCREEN_DECODE) + dst = _offscreenBuffer; + else + dst = _screen->getPageRect(_drawPage, _x, _y, _width, _height); + + if (_currentFrame == _numFrames) { + if (!(_flags & WF_NO_FIRST_FRAME)) { + if (_flags & WF_OFFSCREEN_DECODE) + Screen::decodeFrameDelta(dst, _deltaBuffer); + else + Screen::decodeFrameDeltaPage(dst, _deltaBuffer, _width, (_flags & WF_XOR) == 0); + } + _currentFrame = 0; + } + + // try to reduce the number of needed frame operations + int diffCount = ABS(_currentFrame - frameNum); + int frameStep = 1; + int frameCount; + if (_currentFrame < frameNum) { + frameCount = _numFrames - frameNum + _currentFrame; + if (diffCount > frameCount && !(_flags & WF_NO_LAST_FRAME)) + frameStep = -1; + else + frameCount = diffCount; + } else { + frameCount = _numFrames - _currentFrame + frameNum; + if (frameCount >= diffCount || (_flags & WF_NO_LAST_FRAME)) { + frameStep = -1; + frameCount = diffCount; + } + } + + // process + if (frameStep > 0) { + uint16 cf = _currentFrame; + while (frameCount--) { + cf += frameStep; + processFrame(cf, dst); + if (cf == _numFrames) + cf = 0; + } + } else { + uint16 cf = _currentFrame; + while (frameCount--) { + if (cf == 0) + cf = _numFrames; + processFrame(cf, dst); + cf += frameStep; + } + } + + // display + _currentFrame = frameNum; + if (_flags & WF_OFFSCREEN_DECODE) { + int pageBackUp = _screen->setCurPage(_drawPage); + + int plotFunc = (flags & 0xFF00) >> 12; + int unk1 = flags & 0xFF; + + _screen->copyWsaRect(_x, _y, _width, _height, 0, plotFunc, _offscreenBuffer, unk1, table1, table2); + + _screen->_curPage = pageBackUp; + } +} + +void WSAMovie_v1::processFrame(int frameNum, uint8 *dst) { + if (!_opened) + return; + assert(frameNum <= _numFrames); + const uint8 *src = _frameData + _frameOffsTable[frameNum]; + Screen::decodeFrame4(src, _deltaBuffer, _deltaBufferSize); + if (_flags & WF_OFFSCREEN_DECODE) + Screen::decodeFrameDelta(dst, _deltaBuffer); + else + Screen::decodeFrameDeltaPage(dst, _deltaBuffer, _width, false); +} + +#pragma mark - + +WSAMovieAmiga::WSAMovieAmiga(KyraEngine_v1 *vm) : WSAMovie_v1(vm), _buffer(0) {} + +int WSAMovieAmiga::open(const char *filename, int offscreenDecode, Palette *palBuf) { + int res = WSAMovie_v1::open(filename, offscreenDecode, palBuf); + + if (!res) + return 0; + + _buffer = new uint8[_width * _height]; + assert(_buffer); + return res; +} + +void WSAMovieAmiga::close() { + if (_opened) { + delete[] _buffer; + _buffer = 0; + } + WSAMovie_v1::close(); +} + +void WSAMovieAmiga::displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2) { + if (frameNum >= _numFrames || frameNum < 0 || !_opened) + return; + + _x = x; + _y = y; + _drawPage = pageNum; + + uint8 *dst; + dst = _buffer; + memset(_buffer, 0, _width * _height); + + if (_currentFrame == _numFrames) { + if (!(_flags & WF_NO_FIRST_FRAME)) { + Screen::decodeFrameDelta(dst, _deltaBuffer, true); + Screen::convertAmigaGfx(dst, _width, _height, 5, (_flags & WF_FLIPPED) != 0); + + if (_flags & WF_OFFSCREEN_DECODE) { + dst = _offscreenBuffer; + const uint8 *src = _buffer; + int size = _width * _height; + + for (int i = 0; i < size; ++i) + *dst++ ^= *src++; + + dst = _buffer; + } else { + _screen->copyBlockToPage(_drawPage, _x, _y, _width, _height, _buffer); + } + } + _currentFrame = 0; + } + + // try to reduce the number of needed frame operations + int diffCount = ABS(_currentFrame - frameNum); + int frameStep = 1; + int frameCount; + if (_currentFrame < frameNum) { + frameCount = _numFrames - frameNum + _currentFrame; + if (diffCount > frameCount && !(_flags & WF_NO_LAST_FRAME)) + frameStep = -1; + else + frameCount = diffCount; + } else { + frameCount = _numFrames - _currentFrame + frameNum; + if (frameCount >= diffCount || (_flags & WF_NO_LAST_FRAME)) { + frameStep = -1; + frameCount = diffCount; + } + } + + // process + if (frameStep > 0) { + uint16 cf = _currentFrame; + while (frameCount--) { + cf += frameStep; + processFrame(cf, dst); + if (cf == _numFrames) + cf = 0; + } + } else { + uint16 cf = _currentFrame; + while (frameCount--) { + if (cf == 0) + cf = _numFrames; + processFrame(cf, dst); + cf += frameStep; + } + } + + // display + _currentFrame = frameNum; + if (_flags & WF_OFFSCREEN_DECODE) { + int pageBackUp = _screen->setCurPage(_drawPage); + + int plotFunc = (flags & 0xFF00) >> 12; + int unk1 = flags & 0xFF; + + _screen->copyWsaRect(_x, _y, _width, _height, 0, plotFunc, _offscreenBuffer, unk1, table1, table2); + + _screen->_curPage = pageBackUp; + } +} + +void WSAMovieAmiga::processFrame(int frameNum, uint8 *dst) { + if (!_opened) + return; + assert(frameNum <= _numFrames); + + memset(dst, 0, _width * _height); + + const uint8 *src = _frameData + _frameOffsTable[frameNum]; + Screen::decodeFrame4(src, _deltaBuffer, _deltaBufferSize); + Screen::decodeFrameDelta(dst, _deltaBuffer, true); + Screen::convertAmigaGfx(dst, _width, _height, 5, (_flags & WF_FLIPPED) != 0); + + src = dst; + dst = 0; + int dstPitch = 0; + if (_flags & WF_OFFSCREEN_DECODE) { + dst = _offscreenBuffer; + dstPitch = _width; + } else { + dst = _screen->getPageRect(_drawPage, _x, _y, _width, _height); + dstPitch = Screen::SCREEN_W; + } + + for (int y = 0; y < _height; ++y) { + for (int x = 0; x < _width; ++x) + *dst++ ^= *src++; + dst += dstPitch - _width; + } +} + +#pragma mark - + +WSAMovie_v2::WSAMovie_v2(KyraEngine_v1 *vm) : WSAMovie_v1(vm), _xAdd(0), _yAdd(0) {} + +int WSAMovie_v2::open(const char *filename, int unk1, Palette *palBuf) { + close(); + + uint32 flags = 0; + uint32 fileSize; + uint8 *p = _vm->resource()->fileData(filename, &fileSize); + if (!p) { + warning("couldn't load wsa file: '%s'", filename); + return 0; + } + + const uint8 *wsaData = p; + _numFrames = READ_LE_UINT16(wsaData); wsaData += 2; + _xAdd = (int16)(READ_LE_UINT16(wsaData)); wsaData += 2; + _yAdd = (int16)(READ_LE_UINT16(wsaData)); wsaData += 2; + _width = READ_LE_UINT16(wsaData); wsaData += 2; + _height = READ_LE_UINT16(wsaData); wsaData += 2; + _deltaBufferSize = READ_LE_UINT16(wsaData); wsaData += 2; + _offscreenBuffer = NULL; + _flags = 0; + flags = READ_LE_UINT16(wsaData); wsaData += 2; + + uint32 offsPal = 0; + if (flags & 1) { + offsPal = 0x300; + _flags |= WF_HAS_PALETTE; + if (palBuf) + _screen->loadPalette(wsaData + 8 + ((_numFrames << 2) & 0xFFFF), *palBuf, 0x300); + } + + if (flags & 2) { + if (_vm->gameFlags().use16ColorMode) { + offsPal = 0x30; + _flags |= WF_HAS_PALETTE; + if (palBuf) + _screen->loadPalette(wsaData + 8 + ((_numFrames << 2) & 0xFFFF), *palBuf, 0x30); + } + + _flags |= WF_XOR; + } + + + if (!(unk1 & 2)) { + _flags |= WF_OFFSCREEN_DECODE; + const int offscreenBufferSize = _width * _height; + _offscreenBuffer = new uint8[offscreenBufferSize]; + memset(_offscreenBuffer, 0, offscreenBufferSize); + } + + if (_numFrames & 0x8000) { + warning("Unhandled wsa flags 0x80"); + _flags |= 0x80; + _numFrames &= 0x7FFF; + } + _currentFrame = _numFrames; + + _deltaBuffer = new uint8[_deltaBufferSize]; + memset(_deltaBuffer, 0, _deltaBufferSize); + + // read frame offsets + _frameOffsTable = new uint32[_numFrames + 2]; + _frameOffsTable[0] = 0; + uint32 frameDataOffs = READ_LE_UINT32(wsaData); wsaData += 4; + bool firstFrame = true; + if (frameDataOffs == 0) { + firstFrame = false; + frameDataOffs = READ_LE_UINT32(wsaData); + _flags |= WF_NO_FIRST_FRAME; + } + + for (int i = 1; i < _numFrames + 2; ++i) { + _frameOffsTable[i] = READ_LE_UINT32(wsaData); + if (_frameOffsTable[i]) + _frameOffsTable[i] -= frameDataOffs; + wsaData += 4; + } + + if (!_frameOffsTable[_numFrames + 1]) + _flags |= WF_NO_LAST_FRAME; + + // skip palette + wsaData += offsPal; + + // read frame data + const int frameDataSize = p + fileSize - wsaData; + + _frameData = new uint8[frameDataSize]; + memcpy(_frameData, wsaData, frameDataSize); + + // decode first frame + if (firstFrame) + Screen::decodeFrame4(_frameData, _deltaBuffer, _deltaBufferSize); + + delete[] p; + _opened = true; + + return _numFrames; +} + +} // End of namespace Kyra diff --git a/engines/kyra/graphics/wsamovie.h b/engines/kyra/graphics/wsamovie.h new file mode 100644 index 0000000000..d00aa89af1 --- /dev/null +++ b/engines/kyra/graphics/wsamovie.h @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef KYRA_WSAMOVIE_H +#define KYRA_WSAMOVIE_H + +#include "kyra/kyra_v1.h" + +namespace Kyra { + +class Palette; + +class Movie { +public: + Movie(KyraEngine_v1 *vm) : _vm(vm), _screen(vm->screen()), _opened(false), _x(-1), _y(-1), _drawPage(-1) {} + virtual ~Movie() {} + + virtual bool opened() { return _opened; } + + virtual int xAdd() const { return 0; } + virtual int yAdd() const { return 0; } + + virtual int width() const = 0; + virtual int height() const = 0; + + virtual int open(const char *filename, int offscreen, Palette *palette) = 0; + virtual void close() = 0; + + virtual int frames() = 0; + + virtual void displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2) = 0; + +protected: + KyraEngine_v1 *_vm; + Screen *_screen; + bool _opened; + + int _x, _y; + int _drawPage; +}; + +class WSAMovie_v1 : public Movie { +public: + WSAMovie_v1(KyraEngine_v1 *vm); + virtual ~WSAMovie_v1(); + + int width() const { return _width; } + int height() const { return _height; } + + virtual int open(const char *filename, int offscreen, Palette *palette); + virtual void close(); + + virtual int frames() { return _opened ? _numFrames : -1; } + + virtual void displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2); + + enum WSAFlags { + WF_OFFSCREEN_DECODE = 0x10, + WF_NO_LAST_FRAME = 0x20, + WF_NO_FIRST_FRAME = 0x40, + WF_FLIPPED = 0x80, + WF_HAS_PALETTE = 0x100, + WF_XOR = 0x200 + }; + +protected: + virtual void processFrame(int frameNum, uint8 *dst); + + uint16 _currentFrame; + uint16 _numFrames; + uint16 _width; + uint16 _height; + uint16 _flags; + uint8 *_deltaBuffer; + uint32 _deltaBufferSize; + uint8 *_offscreenBuffer; + uint32 *_frameOffsTable; + uint8 *_frameData; +}; + +class WSAMovieAmiga : public WSAMovie_v1 { +public: + WSAMovieAmiga(KyraEngine_v1 *vm); + int open(const char *filename, int offscreen, Palette *palette); + void close(); + + void displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2); +private: + void processFrame(int frameNum, uint8 *dst); + + uint8 *_buffer; +}; + +class WSAMovie_v2 : public WSAMovie_v1 { +public: + WSAMovie_v2(KyraEngine_v1 *vm); + + int open(const char *filename, int unk1, Palette *palette); + virtual void displayFrame(int frameNum, int pageNum, int x, int y, uint16 flags, const uint8 *table1, const uint8 *table2) { + WSAMovie_v1::displayFrame(frameNum, pageNum, x + _xAdd, y + _yAdd, flags, table1, table2); + } + + int xAdd() const { return _xAdd; } + int yAdd() const { return _yAdd; } + + void setWidth(int w) { _width = w; } + void setHeight(int h) { _height = h; } +protected: + int16 _xAdd; + int16 _yAdd; +}; + +} // End of namespace Kyra + +#endif diff --git a/engines/kyra/gui.cpp b/engines/kyra/gui.cpp deleted file mode 100644 index d9c2f9183d..0000000000 --- a/engines/kyra/gui.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - - -#include "kyra/gui.h" -#include "kyra/kyra_v1.h" -#include "kyra/util.h" - -#include "common/savefile.h" -#include "common/system.h" - - -namespace Kyra { - -GUI::GUI(KyraEngine_v1 *kyra) : _vm(kyra), _screen(kyra->screen()) { - _saveSlotsListUpdateNeeded = true; - _savegameListSize = 0; - _savegameList = 0; -} - -GUI::~GUI() { - if (_savegameList) { - for (int i = 0; i < _savegameListSize; i++) - delete[] _savegameList[i]; - delete[] _savegameList; - _savegameList = 0; - } -} - -void GUI::updateSaveFileList(Common::String targetName, bool excludeQuickSaves) { - Common::String pattern = targetName + ".###"; - Common::StringArray saveFileList = _vm->_saveFileMan->listSavefiles(pattern); - _saveSlots.clear(); - - for (Common::StringArray::const_iterator i = saveFileList.begin(); i != saveFileList.end(); ++i) { - // The last 3 digits of the filename correspond to the save slot. - const int slotNum = atoi(i->c_str() + i->size() - 3); - if (excludeQuickSaves && slotNum >= 990) - continue; - _saveSlots.push_back(slotNum); - } - - if (_saveSlots.begin() == _saveSlots.end()) - return; - - sortSaveSlots(); -} - -void GUI::sortSaveSlots() { - Common::sort(_saveSlots.begin(), _saveSlots.end(), Common::Less()); - if (_saveSlots.size() > 2) - Common::sort(_saveSlots.begin() + 1, _saveSlots.end(), Common::Greater()); -} - -int GUI::getNextSavegameSlot() { - Common::InSaveFile *in; - - int start = _vm->game() == GI_LOL ? 0 : 1; - - for (int i = start; i < 990; i++) { - if ((in = _vm->_saveFileMan->openForLoading(_vm->getSavegameFilename(i)))) - delete in; - else - return i; - } - warning("Didn't save: Ran out of saveGame filenames"); - return 0; -} - -void GUI::updateSaveSlotsList(Common::String targetName, bool force) { - if (!_saveSlotsListUpdateNeeded && !force) - return; - - _saveSlotsListUpdateNeeded = false; - - if (_savegameList) { - for (int i = 0; i < _savegameListSize; i++) - delete[] _savegameList[i]; - delete[] _savegameList; - } - - updateSaveFileList(targetName, true); - int numSaves = _savegameListSize = _saveSlots.size(); - bool allowEmptySlots = (_vm->game() == GI_EOB1 || _vm->game() == GI_EOB2); - - if (_savegameListSize) { - if (allowEmptySlots) - _savegameListSize = 990; - - KyraEngine_v1::SaveHeader header; - Common::InSaveFile *in; - - _savegameList = new char*[_savegameListSize]; - memset(_savegameList, 0, _savegameListSize * sizeof(char *)); - - for (int i = 0; i < numSaves; i++) { - in = _vm->openSaveForReading(_vm->getSavegameFilename(targetName, _saveSlots[i]).c_str(), header, targetName == _vm->_targetName); - char **listEntry = &_savegameList[allowEmptySlots ? _saveSlots[i] : i]; - if (in) { - *listEntry = new char[header.description.size() + 1]; - Common::strlcpy(*listEntry, header.description.c_str(), header.description.size() + 1); - Util::convertISOToDOS(*listEntry); - delete in; - } else { - *listEntry = 0; - error("GUI::updateSavegameList(): Unexpected missing save file for slot: %d.", _saveSlots[i]); - } - } - - } else { - _savegameList = 0; - } -} - -} // End of namespace Kyra diff --git a/engines/kyra/gui.h b/engines/kyra/gui.h deleted file mode 100644 index 3e2bdc04cd..0000000000 --- a/engines/kyra/gui.h +++ /dev/null @@ -1,137 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef KYRA_GUI_H -#define KYRA_GUI_H - -#include "kyra/kyra_v1.h" -#include "kyra/screen.h" - -#include "common/ptr.h" -#include "common/array.h" -#include "common/func.h" - -#include "graphics/surface.h" - -namespace Kyra { - -#define BUTTON_FUNCTOR(type, x, y) Button::Callback(new Common::Functor1Mem