Merge branch 'master' into win_install_iso

This commit is contained in:
Stephen E. Baker
2021-05-19 22:26:19 -04:00
185 changed files with 8589 additions and 3146 deletions

View File

@@ -1,40 +0,0 @@
#### Describe the issue
#### Steps to Reproduce
1. *Load save game attached to this ticket*
2.
3.
#### Save Game
*It is often useful for us if you have a save game from shortly before the
issue occurs that can be used to recreate the problem. Sometimes a save that
shows the issue happening may be useful as well. As GitHub currently only
supports uploading of images, you will have to upload your your savegames to
an another source such as [Google Drive](https://drive.google.com),
[Dropbox](https://dropbox.com), or
[SkyDrive/OneDrive](https://onedrive.live.com). Add the link to the file to
your issue and make the file(s) public, so we can access your gamelog or
savegame. The most important thing is to not remove these files after you
uploaded them!*
#### Expected Behavior
#### System Information
**CorsixTH Version:** *e.g. 0.50 or 7d886f35ca*
**Operating System:** *e.g. Windows 10*
**Theme Hospital Version:** *CD, GOG.com, Origin, or Demo*
#### Gamelog.txt
*For information about where to find gamelog.txt see:
https://github.com/CorsixTH/CorsixTH/wiki/Frequently-Asked-Questions#where-do-i-find-the-configuration-or-the-gamelog-file*
```
Paste gamelog.txt output here
```
### Additional Info
*Paste any screen shots or other additional information that might help
illustrate the problem.*

46
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,46 @@
---
name: Bug report
about: Create a report to help us improve
title: "[Bug] Give a summary of the problem"
labels: bug
assignees: ''
---
#### Describe the issue
#### Steps to Reproduce
1. *Load save game attached to this ticket*
2.
3.
#### Save Game
<!-- It is often useful for us if you have a save game from shortly before the issue occurs that can be used to recreate the problem. Sometimes a save that shows the issue happening may be useful as well.
You can upload your save by putting it in zip file, then copy/pasting it directly into this text area. You can also click the "Attach files by dragging & dropping, selecting or pasting them" text at the bottom to upload the zip file.
Alternatively, you can use an external source such as Google Drive, Dropbox, or OneDrive and sharing the public link below to access your save game. The most important thing is to not remove these files after you upload them. -->
#### Expected Behavior
#### System Information
**CorsixTH Version:** *e.g. 0.50 or 7d886f35ca*
**Operating System:** *e.g. Windows 10*
**Theme Hospital Version:** *CD, GOG.com, Origin, or Demo*
#### Gamelog.txt
<!-- For information about where to find gamelog.txt see:
https://github.com/CorsixTH/CorsixTH/wiki/Frequently-Asked-Questions#where-do-i-find-the-configuration-or-the-gamelog-file
-->
```
Paste gamelog.txt output here
```
### Additional Info
*Paste any screen shots or other additional information that might help
illustrate the problem.*

View File

@@ -0,0 +1,28 @@
---
name: Feature request
about: Suggest an idea for this project
title: Give a feature/enhancement summary
labels: enhancement
assignees: ''
---
**What feature or enhancement would you like to add?**
Include here a description of what feature/enhancement you would like.
**How would this feature benefit the gameplay of CorsixTH?**
This will help developers decide if it is a worthwhile endeavour.
**Was the feature in the original game?**
Yes/No
**What version of CorsixTH are you using?**
<!-- e.g. 0.60 or build number
You can check and download the latest stable release at https://github.com/CorsixTH/CorsixTH/releases
-->
**Does this feature/enhancement relate to a current problem?**
Give a summary of the problem referencing an issue number if one exists.
**Additional information**
Add any other information or screenshots about the feature request here.

6
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,6 @@
*Fixes #*
**Describe what the proposed change does**
-
-
-

151
.github/workflows/Linux.yml vendored Normal file
View File

@@ -0,0 +1,151 @@
---
name: Linux and Tests
on: # yamllint disable-line rule:trurhy
push:
branches-ignore:
- 'gh-pages'
pull_request:
jobs:
Linux:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
include:
- static_analysis: 0
docs: 0
cmake: 1
lua: LuaJIT
luac: luac5.1
animview: and AnimView
packages: libluajit-5.1-dev luajit libwxgtk3.0-gtk3-dev
cmakejit: '-DBUILD_ANIMVIEWER=ON -DWITH_LUAJIT=ON -DLUA_LIBRARY=/usr/lib/x86_64-linux-gnu/libluajit-5.1.so'
- static_analysis: 0
docs: 0
lua: Lua 5.1
luac: luac5.1
packages: liblua5.1-dev lua5.1
- static_analysis: 1
docs: 1
lua: Lua 5.3
luac: luac5.3
packages: liblua5.3-dev lua5.3
catch2: -DENABLE_UNIT_TESTS=ON
name: Linux and Tests on ${{ matrix.lua }}
steps:
- uses: actions/checkout@v2 # Keeps the git OAuth token after checkout
- name: Install ${{ matrix.lua }} ${{ matrix.animview }}
run: |
sudo apt-get update
sudo apt-get install ${{ matrix.packages }} luarocks
# Required for LDocGen
sudo luarocks install lpeg
# Required for lua lint check
sudo luarocks install luacheck
# Required for lua unit tests
sudo luarocks install busted
- name: Install CorsixTH requirements
run: |
sudo apt-get install libsdl2-dev libsdl2-mixer-dev \
libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev
- name: Install static analysis requirements
if: matrix.static_analysis
run: |
sudo apt-get install doxygen yamllint clang-tidy-9
sudo pip3 install -I codespell==2.0 cmakelint==1.4
# Build catch2
git clone --quiet https://github.com/catchorg/Catch2 \
--branch v2.13.2 --depth=1
cd Catch2
cmake . -Bbuild -DBUILD_TESTING=OFF
cd build
sudo make install
cd ../../
rm -rf Catch2
- name: Install CMake 3.5
if: matrix.cmake
run: |
# Install CMake version 3.5, the oldest CorsixTH-supported version, which does not support Lua 5.4
curl -L https://github.com/Kitware/CMake/releases/download/v3.5.0/cmake-3.5.0-Linux-i386.sh -o cmake.sh
sudo bash cmake.sh --prefix=/usr/local/ --exclude-subdir --skip-license
cmake --version
- name: Create CorsixTH ${{ matrix.animview }} makefiles with ${{ matrix.lua }}
run: |
cmake . -G"Unix Makefiles" -Bbuild --debug-output \
-DWITH_AUDIO=ON -DWITH_MOVIES=ON ${{ matrix.catch2 }} \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON ${{ matrix.cmakejit }}
- name: Build CorsixTH ${{ matrix.animview }} with ${{ matrix.lua }}
run: |
cmake --build build/ -- VERBOSE=1
sudo cmake --build build/ -- install
- name: Run ${{ matrix.lua }} tests
run: |
# Validate lua files
find CorsixTH -name '*.lua' -print0 | xargs -0 -I{} ${{ matrix.luac }} -p {}
# Run lua lint check
luacheck --quiet --codes --ranges CorsixTH
# Run lua unit tests
busted --verbose --directory=CorsixTH/Luatest
- name: Run simple code tests
if: matrix.static_analysis
run: |
# Check if there are trailing whitespaces.
python3 scripts/check_trailing_whitespaces.py
# Check for incorrectly encoded files.
python3 scripts/check_language_files_not_BOM.py
# Check if there are lua classes with invalid/improper declarations.
python3 scripts/check_lua_classes.py
# Check for tabs (should be spaces)
grep -Ire '\t' CorsixTH/Lua --exclude-dir=languages
# Run CPP Tests
cd build/CorsixTH
ctest --extra-verbose --build-config Release --output-on-failure
cd ../..
# Build and package LevelEdit
ant -buildfile LevelEdit/build.xml dist
# Run codespell
codespell --enable-colors --quiet-level 2 --skip="languages,corsix-th.6" \
-L sav,unexpect,persistance,defin,uint,inout,currenty,blong \
AnimView CorsixTH CorsixTH/Lua/languages/english.lua LevelEdit libs
# Cmake file linter
cmakelint --filter=-linelength AnimView/CMakeLists.txt CMakeLists.txt CorsixTH/CMakeLists.txt \
CorsixTH/CppTest/CMakeLists.txt CorsixTH/Src/CMakeLists.txt CorsixTH/SrcUnshared/CMakeLists.txt \
libs/CMakeLists.txt libs/rnc/CMakeLists.txt \
CMake/GenerateDoc.cmake CMake/PrecompiledDeps.cmake CMake/VcpkgDeps.cmake
# Validate these build files
yamllint --config-data "rules: {line-length: disable}" .github/workflows/Linux.yml
shellcheck --shell sh scripts/macos_luarocks
- name: Run clang code tests
if: matrix.static_analysis
run: |
# Check cpp format
clang-format-9 -i CorsixTH/Src/*.cpp CorsixTH/Src/*.h AnimView/*.cpp \
AnimView/*.h libs/rnc/*.cpp libs/rnc/*.h CorsixTH/SrcUnshared/main.cpp
git diff --exit-code
# Clang-tidy linter
clang-tidy-9 -p build --warnings-as-errors=\* \
CorsixTH/Src/*.c CorsixTH/Src/*.cpp libs/rnc/*.cpp CorsixTH/SrcUnshared/main.cpp
- name: Generate documentation
if: matrix.docs
run: |
cmake --build build/ --target -- doc
- name: Upload documentation
if: github.ref == 'refs/heads/master' && github.repository == 'CorsixTH/CorsixTH' && matrix.docs
run: |
git config user.email "documentationbot@corsixth.com"
git config user.name "Docs Bot"
git fetch origin gh-pages
git checkout --force gh-pages
rsync --recursive build/doc/ .
git add animview/ corsixth_engine/ corsixth_lua/ index.html leveledit/
if ! git diff --cached --exit-code; then
git commit --message "Documentation from $(git rev-parse --short master) [no CI]"
git push origin gh-pages
else
echo "No change to documentation."
fi

View File

@@ -38,10 +38,11 @@ globals = { -- Globals
"GrimReaper", "Hospital", "Humanoid", "HumanoidRawWalk",
"Inspector", "LoadGame", "LoadGameFile", "Litter", "Machine",
"Map", "MoviePlayer", "NoRealClass", "Object", "ParentClass",
"Patient", "Plant", "Queue", "ResearchDepartment", "Room",
"Patient", "Plant", "PlayerHospital", "Queue", "ResearchDepartment", "Room",
"SaveGame", "SaveGameFile", "Staff", "StaffProfile", "StaffRoom",
"Strings", "SwingDoor", "TheApp", "TreeControl", "Vip", "Window",
"World", "Date", "Doctor", "Handyman", "Nurse", "Receptionist",
"Cheats",
-- UI
"UI", "UIAdviser", "UIAnnualReport", "UIAudio", "UIBankManager",
@@ -109,7 +110,7 @@ end
-- W113: accessing undefined variable XYZ
-- W314: value assigned to field XYZ is unused
for _, lng in ipairs({"brazilian_portuguese", "czech", "danish", "developer",
"dutch", "english", "finnish", "french", "german", "hungarian",
"dutch", "english", "finnish", "french", "german", "greek", "hungarian",
"iberic_portuguese", "italian", "korean", "norwegian", "original_strings",
"polish", "russian", "simplified_chinese", "spanish", "swedish",
"traditional_chinese"}) do
@@ -238,3 +239,5 @@ add_ignore("CorsixTH/Lua/window.lua", "542")
add_ignore("CorsixTH/Lua/world.lua", "212")
add_ignore("CorsixTH/Lua/world.lua", "542")
add_ignore("CorsixTH/Luatest/non_strict.lua", "212")
add_ignore("CorsixTH/Luatest/spec/utility_spec.lua", "121")
add_ignore("CorsixTH/CorsixTH.lua", "143") -- luacheck is missing 5.4 debug functions

View File

@@ -1,88 +0,0 @@
dist: bionic
language: cpp
compiler: gcc
addons:
apt:
sources:
packages:
- libssl-dev
- luarocks
- doxygen
- sshpass
- libavcodec-dev
- libavformat-dev
- libavutil-dev
- libswresample-dev
- libswscale-dev
- libsdl2-dev
- libsdl2-mixer-dev
- libfreetype6-dev
- libwxgtk3.0-gtk3-dev
- clang-tidy-8
before_install:
# Bionic does not have catch2 in its repo so clone and install locally
- cd $HOME
- git clone https://github.com/catchorg/Catch2.git --depth=1
- cd Catch2
- cmake -Bbuild -H. -DBUILD_TESTING=OFF
- cd build && sudo make install
- cd ${TRAVIS_BUILD_DIR}
- luarocks --local install --server=http://luarocks.org luasec OPENSSL_DIR=/usr OPENSSL_LIBDIR=/usr/lib/x86_64-linux-gnu/
# Required for LDocGen
- luarocks --local install lpeg
- luarocks --local install luafilesystem
- luarocks --local install luasocket
- luarocks --local install luacheck 0.19.1
# Required for lua unit tests
- luarocks --local install busted
- mkdir $TRAVIS_BUILD_DIR/LevelEdit/bin
install:
- cd $TRAVIS_BUILD_DIR
# Create unix makefiles
- cmake -DLUA_PROGRAM_PATH=`which lua` -DENABLE_UNIT_TESTS=ON -DWITH_AUDIO=ON -DWITH_MOVIES=ON -DWITH_LUAJIT=OFF -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -Bfresh -H. --debug-output
before_script:
# Don't ask for confirmation when using scp
- echo -e "Host armedpineapple.co.uk\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
- echo -e "Host server2.armedpineapple.co.uk\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
script:
# Check if there are trailing whitespaces.
- ${TRAVIS_BUILD_DIR}/scripts/check_trailing_whitespaces.py $TRAVIS_BUILD_DIR
# Check for incorrectly encoded language files.
- ${TRAVIS_BUILD_DIR}/scripts/check_language_files_not_BOM.py $TRAVIS_BUILD_DIR/CorsixTH/Lua/languages
# Check if there are lua classes with invalid/improper declarations.
- ${TRAVIS_BUILD_DIR}/scripts/check_lua_classes.py
# Check cpp format
- cd ${TRAVIS_BUILD_DIR}
- clang-format -i CorsixTH/Src/*{.cpp,.h} AnimView/*{.cpp,.h} libs/rnc/*{.cpp,.h} CorsixTH/SrcUnshared/main.cpp
# clang-tidy check. The valist test is buggy in this environment (https://bugs.llvm.org/show_bug.cgi?id=41311)
- clang-tidy-8 -p $TRAVIS_BUILD_DIR/fresh --warnings-as-errors=\*,-clang-analyzer-valist.Uninitialized CorsixTH/Src/*{.cpp,.c} libs/rnc/*.cpp CorsixTH/SrcUnshared/main.cpp
- git diff --exit-code
# Build CorsixTH
- cd fresh
- make VERBOSE=1
# Validate lua files
- find $TRAVIS_BUILD_DIR -path $TRAVIS_BUILD_DIR/CorsixTH/Lua/languages -prune -o -name '*.lua' -print0 | xargs -0 luac -p --
- cd $TRAVIS_BUILD_DIR
- /home/travis/.luarocks/bin/luacheck -q .
# Run lua unit tests
- cd $TRAVIS_BUILD_DIR/CorsixTH/Luatest
- eval `luarocks --local path`
- LUA_PATH="../Lua/?.lua;$LUA_PATH" /home/travis/.luarocks/bin/busted
# Run CPP Tests
- cd $TRAVIS_BUILD_DIR/fresh/CorsixTH
- eval 'ctest -VV'
# Build LevelEdit
- cd $TRAVIS_BUILD_DIR/LevelEdit && ant dist
# Build documentation
- cd ${TRAVIS_BUILD_DIR}/fresh && make doc
after_success:
# Upload new docs
- if [ $TRAVIS_REPO_SLUG == "CorsixTH/CorsixTH" -a $TRAVIS_BRANCH == "master" ]; then sshpass -p "$SCP_PASS" scp -r -v -P12349 $TRAVIS_BUILD_DIR/fresh/doc/* cthbuilder@server2.armedpineapple.co.uk:/home/cthbuilder/docs/; fi;
env:
global:
secure: "mPtzSeDJKVeUu6KHJEbmHa91O+QK2XM0advYdr+13yr83w3C7cGFFjWgqzirsFUHVqPgdtSJIkom0DIxX7JtRoBZmt/xon+zfLq+Q4aSkJoYezWBqp2gavS8o1kSjtW7XuIuq995yjWvn7nehyoTYkKNtz/EzX5ZIVZ/iX73iyo="

View File

@@ -1,3 +1,9 @@
# Sanity check
if(CORSIX_TH_DONE_TOP_LEVEL_CMAKE)
else()
message(FATAL_ERROR "Please run CMake from the top-level directory instead of here.")
endif()
# Project Declaration
project(AnimView)
@@ -34,7 +40,7 @@ if(APPLE)
)
set(MACOSX_BUNDLE_ICON_FILE Icon.icns)
add_executable(
add_executable(
AnimView
MACOSX_BUNDLE
${animview_source_files}
@@ -44,8 +50,8 @@ if(APPLE)
set_target_properties(AnimView PROPERTIES LINK_FLAGS_MINSIZEREL "-dead_strip")
set_target_properties(AnimView PROPERTIES XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks")
else()
add_executable(
AnimView
add_executable(
AnimView
WIN32 # This prevents the dos console showing up
${animview_source_files}
)
@@ -70,7 +76,7 @@ if(wxWidgets_FOUND)
target_link_libraries(AnimView ${wxWidgets_LIBRARIES})
message(" wxWidgets found")
else()
message(FATAL_ERROR "error: wxWdigets library not found, it is required to build")
message(FATAL_ERROR "error: wxWidgets library not found, it is required to build")
message("Make sure the path is correctly defined or set the environment variable WXWIN to the correct location")
endif()
@@ -81,14 +87,14 @@ endif()
if(APPLE)
install(TARGETS AnimView BUNDLE DESTINATION .)
# Fix the OS X bundle to include required libraries (create a redistributable app)
# Fix the macOS bundle to include required libraries (create a redistributable app)
install(CODE "
INCLUDE(BundleUtilities)
SET(BU_CHMOD_BUNDLE_ITEMS ON)
FIXUP_BUNDLE(${CMAKE_INSTALL_PREFIX}/AnimView.app \"\" \"\")
FIXUP_BUNDLE(\"${CMAKE_INSTALL_PREFIX}/AnimView.app\" \"\" \"\")
")
else()
install(TARGETS AnimView RUNTIME DESTINATION AnimView)
install(FILES LICENSE.txt DESTINATION AnimView )
install(FILES ../LICENSE.txt DESTINATION AnimView)
endif()

View File

@@ -27,6 +27,7 @@ SOFTWARE.
#include <wx/bitmap.h>
#include <wx/dcclient.h>
#include <wx/dcmemory.h>
#include <wx/defs.h>
#include <wx/dir.h>
#include <wx/dirdlg.h>
#include <wx/filename.h>
@@ -54,7 +55,7 @@ BEGIN_EVENT_TABLE(frmMain, wxFrame)
EVT_BUTTON(ID_SEARCH_LAYER_ID, frmMain::_onSearchLayerId)
EVT_BUTTON(ID_SEARCH_FRAME, frmMain::_onSearchFrame)
EVT_BUTTON(ID_SEARCH_SOUND, frmMain::_onSearchSoundIndex)
EVT_LISTBOX(ID_SEARCH_RESULTS, frmMain::_onGotoSearchResult)
EVT_LISTBOX(ID_SEARCH_RESULTS, frmMain::_onAnimChar)
EVT_RADIOBUTTON(ID_GHOST_0, frmMain::_onGhostFileChange)
EVT_RADIOBUTTON(ID_GHOST_1, frmMain::_onGhostFileChange)
EVT_RADIOBUTTON(ID_GHOST_2, frmMain::_onGhostFileChange)
@@ -66,25 +67,40 @@ BEGIN_EVENT_TABLE(frmMain, wxFrame)
EVT_CHECKBOX(ID_DRAW_COORDINATES, frmMain::_onToggleDrawCoordinates)
END_EVENT_TABLE()
namespace {
constexpr int make_id(int layer, int id) {
return frmMain::ID_LAYER_CHECKS + (layer * 25) + id;
}
} // namespace
frmMain::frmMain()
: wxFrame(nullptr, wxID_ANY, L"Theme Hospital Animation Viewer") {
wxSizer* pMainSizer = new wxBoxSizer(wxHORIZONTAL);
wxSizer* pSidebarSizer = new wxBoxSizer(wxVERTICAL);
#define def wxDefaultPosition, wxDefaultSize
wxSizerFlags fillSizerFlags(0);
fillSizerFlags.Expand().Border(wxALL, 0);
wxSizerFlags fillBorderSizerFlags(0);
fillBorderSizerFlags.Expand().Border(wxALL, 1);
wxSizerFlags buttonSizerFlags(0);
buttonSizerFlags.Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 1);
wxStaticBoxSizer* pThemeHospital =
new wxStaticBoxSizer(wxHORIZONTAL, this, L"Theme Hospital");
pThemeHospital->Add(new wxStaticText(this, wxID_ANY, L"Directory:"), 0,
wxALIGN_CENTER_VERTICAL | wxALL, 1);
pThemeHospital->Add(
m_txtTHPath = new wxTextCtrl(this, wxID_ANY, L"", def, wxTE_CENTRE), 1,
wxALIGN_CENTER_VERTICAL | wxALL, 1);
pThemeHospital->Add(new wxButton(this, ID_BROWSE, L"Browse..."), 0,
wxALIGN_CENTER_VERTICAL | wxALL, 1);
pThemeHospital->Add(new wxButton(this, ID_LOAD, L"Load"), 0,
wxALIGN_CENTER_VERTICAL | wxALL, 1);
pSidebarSizer->Add(pThemeHospital, 0, wxEXPAND | wxALL, 0);
new wxStaticText(this, wxID_ANY, L"Directory:"),
wxSizerFlags(0).Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 1));
pThemeHospital->Add(
m_txtTHPath = new wxTextCtrl(this, wxID_ANY, L"", wxDefaultPosition,
wxDefaultSize, wxTE_CENTRE),
wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 1));
pThemeHospital->Add(new wxButton(this, ID_BROWSE, L"Browse..."),
buttonSizerFlags);
pThemeHospital->Add(new wxButton(this, ID_LOAD, L"Load"), buttonSizerFlags);
pSidebarSizer->Add(pThemeHospital, fillSizerFlags);
wxStaticBoxSizer* pPalette =
new wxStaticBoxSizer(wxVERTICAL, this, L"Palette");
@@ -95,280 +111,274 @@ frmMain::frmMain()
pPaletteTop->Add(new wxRadioButton(this, ID_GHOST_3, L"Ghost 66"), 1);
m_iGhostFile = 0;
m_iGhostIndex = 0;
pPalette->Add(pPaletteTop, 0, wxEXPAND | wxALL, 1);
pPalette->Add(new wxSpinCtrl(this, wxID_ANY, wxEmptyString, def,
wxSP_ARROW_KEYS | wxSP_WRAP, 0, 255),
0, wxALIGN_CENTER | wxALL, 1);
pSidebarSizer->Add(pPalette, 0, wxEXPAND | wxALL, 0);
pPalette->Add(pPaletteTop, fillBorderSizerFlags);
pPalette->Add(
new wxSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxDefaultSize, wxSP_ARROW_KEYS | wxSP_WRAP, 0, 255),
wxSizerFlags(0).Center().Border(wxALL, 1));
pSidebarSizer->Add(pPalette, fillSizerFlags);
wxStaticBoxSizer* pAnimation =
new wxStaticBoxSizer(wxHORIZONTAL, this, L"Animation");
pAnimation->Add(new wxButton(this, ID_FIRST_ANIM, L"<<", def, wxBU_EXACTFIT),
0, wxALIGN_CENTER_VERTICAL | wxALL, 1);
pAnimation->Add(new wxButton(this, ID_PREV_ANIM, L"<", def, wxBU_EXACTFIT), 0,
wxALIGN_CENTER_VERTICAL | wxALL, 1);
pAnimation->Add(m_txtAnimIndex = new wxTextCtrl(this, ID_ANIM_INDEX, L"0",
def, wxTE_CENTRE),
1, wxALIGN_CENTER_VERTICAL | wxALL, 1);
pAnimation->Add(new wxStaticText(this, wxID_ANY, L"of"), 0,
wxALIGN_CENTER_VERTICAL | wxALL, 1);
pAnimation->Add(m_txtAnimCount = new wxTextCtrl(this, wxID_ANY, L"?", def,
wxTE_CENTRE | wxTE_READONLY),
1, wxALIGN_CENTER_VERTICAL | wxALL, 1);
pAnimation->Add(new wxButton(this, ID_NEXT_ANIM, L">", def, wxBU_EXACTFIT), 0,
wxALIGN_CENTER_VERTICAL | wxALL, 1);
pAnimation->Add(new wxButton(this, ID_LAST_ANIM, L">>", def, wxBU_EXACTFIT),
0, wxALIGN_CENTER_VERTICAL | wxALL, 1);
pSidebarSizer->Add(pAnimation, 0, wxEXPAND | wxALL, 0);
pAnimation->Add(new wxButton(this, ID_FIRST_ANIM, L"<<", wxDefaultPosition,
wxDefaultSize, wxBU_EXACTFIT),
buttonSizerFlags);
pAnimation->Add(new wxButton(this, ID_PREV_ANIM, L"<", wxDefaultPosition,
wxDefaultSize, wxBU_EXACTFIT),
buttonSizerFlags);
m_txtAnimIndex = new wxTextCtrl(this, ID_ANIM_INDEX, L"0", wxDefaultPosition,
wxDefaultSize, wxTE_CENTRE);
pAnimation->Add(
m_txtAnimIndex,
wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 1));
pAnimation->Add(
new wxStaticText(this, wxID_ANY, L"of"),
wxSizerFlags(0).Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 1));
m_txtAnimCount = new wxTextCtrl(this, wxID_ANY, L"?", wxDefaultPosition,
wxDefaultSize, wxTE_CENTRE | wxTE_READONLY);
pAnimation->Add(
m_txtAnimCount,
wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 1));
pAnimation->Add(new wxButton(this, ID_NEXT_ANIM, L">", wxDefaultPosition,
wxDefaultSize, wxBU_EXACTFIT),
buttonSizerFlags);
pAnimation->Add(new wxButton(this, ID_LAST_ANIM, L">>", wxDefaultPosition,
wxDefaultSize, wxBU_EXACTFIT),
buttonSizerFlags);
pSidebarSizer->Add(pAnimation, fillSizerFlags);
wxStaticBoxSizer* pFrame = new wxStaticBoxSizer(wxHORIZONTAL, this, L"Frame");
pFrame->Add(new wxButton(this, ID_PREV_FRAME, L"<", def, wxBU_EXACTFIT), 0,
wxALIGN_CENTER_VERTICAL | wxALL, 1);
pFrame->Add(new wxButton(this, ID_PREV_FRAME, L"<", wxDefaultPosition,
wxDefaultSize, wxBU_EXACTFIT),
buttonSizerFlags);
pFrame->Add(
m_txtFrameIndex = new wxTextCtrl(this, wxID_ANY, L"0", def, wxTE_CENTRE),
1, wxALIGN_CENTER_VERTICAL | wxALL, 1);
pFrame->Add(new wxStaticText(this, wxID_ANY, L"of", def, wxALIGN_CENTRE), 0,
wxALIGN_CENTER_VERTICAL | wxALL, 1);
pFrame->Add(m_txtFrameCount = new wxTextCtrl(this, wxID_ANY, L"?", def,
wxTE_CENTRE | wxTE_READONLY),
1, wxALIGN_CENTER_VERTICAL | wxALL, 1);
pFrame->Add(new wxButton(this, ID_NEXT_FRAME, L">", def, wxBU_EXACTFIT), 0,
wxALIGN_CENTER_VERTICAL | wxALL, 1);
pFrame->Add(m_btnPlayPause = new wxButton(this, ID_PLAY_PAUSE, L"Pause"), 1,
wxALIGN_CENTER_VERTICAL | wxALL, 1);
m_txtFrameIndex = new wxTextCtrl(this, wxID_ANY, L"0", wxDefaultPosition,
wxDefaultSize, wxTE_CENTRE),
wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 1));
pFrame->Add(new wxStaticText(this, wxID_ANY, L"of", wxDefaultPosition,
wxDefaultSize, wxALIGN_CENTRE),
wxSizerFlags(0).Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 1));
m_txtFrameCount = new wxTextCtrl(this, wxID_ANY, L"?", wxDefaultPosition,
wxDefaultSize, wxTE_CENTRE | wxTE_READONLY);
pFrame->Add(m_txtFrameCount,
wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 1));
pFrame->Add(new wxButton(this, ID_NEXT_FRAME, L">", wxDefaultPosition,
wxDefaultSize, wxBU_EXACTFIT),
buttonSizerFlags);
m_btnPlayPause = new wxButton(this, ID_PLAY_PAUSE, L"Pause");
pFrame->Add(m_btnPlayPause,
wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL).Border(wxALL, 1));
m_bPlayingAnimation = true;
// m_bPlayingAnimation = false;
pSidebarSizer->Add(pFrame, 0, wxEXPAND | wxALL, 0);
pSidebarSizer->Add(pFrame, fillSizerFlags);
wxSizerFlags layerSizerFlags(0);
layerSizerFlags.Align(wxALIGN_CENTER).Border(wxALL, 1);
#define ID(layer, id) (ID_LAYER_CHECKS + (layer)*25 + (id))
wxStaticBoxSizer* pLayer0 =
new wxStaticBoxSizer(wxHORIZONTAL, this, L"Layer 0 (Patient Head)");
pLayer0->Add(new wxCheckBox(this, ID(0, 0), L"0"), 0, wxALIGN_CENTER | wxALL,
1);
pLayer0->Add(new wxCheckBox(this, ID(0, 2), L"2"), 0, wxALIGN_CENTER | wxALL,
1);
pLayer0->Add(new wxCheckBox(this, ID(0, 4), L"4"), 0, wxALIGN_CENTER | wxALL,
1);
pLayer0->Add(new wxCheckBox(this, ID(0, 6), L"6"), 0, wxALIGN_CENTER | wxALL,
1);
pLayer0->Add(new wxCheckBox(this, ID(0, 8), L"8"), 0, wxALIGN_CENTER | wxALL,
1);
pLayer0->Add(new wxCheckBox(this, ID(0, 10), L"10"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer0->Add(new wxCheckBox(this, ID(0, 12), L"12"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer0->Add(new wxCheckBox(this, ID(0, 14), L"14"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer0->Add(new wxCheckBox(this, ID(0, 16), L"16"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer0->Add(new wxCheckBox(this, ID(0, 18), L"18"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer0->Add(new wxCheckBox(this, ID(0, 20), L"20"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer0->Add(new wxCheckBox(this, ID(0, 22), L"22"), 0,
wxALIGN_CENTER | wxALL, 1);
pSidebarSizer->Add(pLayer0, 0, wxEXPAND | wxALL, 0);
pLayer0->Add(new wxCheckBox(this, make_id(0, 0), L"0"), layerSizerFlags);
pLayer0->Add(new wxCheckBox(this, make_id(0, 2), L"2"), layerSizerFlags);
pLayer0->Add(new wxCheckBox(this, make_id(0, 4), L"4"), layerSizerFlags);
pLayer0->Add(new wxCheckBox(this, make_id(0, 6), L"6"), layerSizerFlags);
pLayer0->Add(new wxCheckBox(this, make_id(0, 8), L"8"), layerSizerFlags);
pLayer0->Add(new wxCheckBox(this, make_id(0, 10), L"10"), layerSizerFlags);
pLayer0->Add(new wxCheckBox(this, make_id(0, 12), L"12"), layerSizerFlags);
pLayer0->Add(new wxCheckBox(this, make_id(0, 14), L"14"), layerSizerFlags);
pLayer0->Add(new wxCheckBox(this, make_id(0, 16), L"16"), layerSizerFlags);
pLayer0->Add(new wxCheckBox(this, make_id(0, 18), L"18"), layerSizerFlags);
pLayer0->Add(new wxCheckBox(this, make_id(0, 20), L"20"), layerSizerFlags);
pLayer0->Add(new wxCheckBox(this, make_id(0, 22), L"22"), layerSizerFlags);
pSidebarSizer->Add(pLayer0, fillSizerFlags);
wxStaticBoxSizer* pLayer1 =
new wxStaticBoxSizer(wxHORIZONTAL, this, L"Layer 1 (Patient Clothes)");
pLayer1->Add(new wxCheckBox(this, ID(1, 0), L"0"), 0, wxALIGN_CENTER | wxALL,
1);
pLayer1->Add(new wxCheckBox(this, ID(1, 2), L"2 (A)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer1->Add(new wxCheckBox(this, ID(1, 4), L"4 (B)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer1->Add(new wxCheckBox(this, ID(1, 6), L"6 (C)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer1->Add(new wxCheckBox(this, ID(1, 8), L"8"), 0, wxALIGN_CENTER | wxALL,
1);
pLayer1->Add(new wxCheckBox(this, ID(1, 10), L"10"), 0,
wxALIGN_CENTER | wxALL, 1);
pSidebarSizer->Add(pLayer1, 0, wxEXPAND | wxALL, 0);
pLayer1->Add(new wxCheckBox(this, make_id(1, 0), L"0"), layerSizerFlags);
pLayer1->Add(new wxCheckBox(this, make_id(1, 2), L"2 (A)"), layerSizerFlags);
pLayer1->Add(new wxCheckBox(this, make_id(1, 4), L"4 (B)"), layerSizerFlags);
pLayer1->Add(new wxCheckBox(this, make_id(1, 6), L"6 (C)"), layerSizerFlags);
pLayer1->Add(new wxCheckBox(this, make_id(1, 8), L"8"), layerSizerFlags);
pLayer1->Add(new wxCheckBox(this, make_id(1, 10), L"10"), layerSizerFlags);
pSidebarSizer->Add(pLayer1, fillSizerFlags);
wxStaticBoxSizer* pLayer2 = new wxStaticBoxSizer(
wxHORIZONTAL, this, L"Layer 2 (Bandages / Patient Accessory)");
pLayer2->Add(new wxCheckBox(this, ID(2, 2), L"2 (Head / Alt Shoes)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer2->Add(new wxCheckBox(this, ID(2, 4), L"4 (Arm / Hat)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer2->Add(new wxCheckBox(this, ID(2, 6), L"6"), 0, wxALIGN_CENTER | wxALL,
1);
pSidebarSizer->Add(pLayer2, 0, wxEXPAND | wxALL, 0);
pLayer2->Add(new wxCheckBox(this, make_id(2, 2), L"2 (Head / Alt Shoes)"),
layerSizerFlags);
pLayer2->Add(new wxCheckBox(this, make_id(2, 4), L"4 (Arm / Hat)"),
layerSizerFlags);
pLayer2->Add(new wxCheckBox(this, make_id(2, 6), L"6"), layerSizerFlags);
pSidebarSizer->Add(pLayer2, fillSizerFlags);
wxStaticBoxSizer* pLayer3 =
new wxStaticBoxSizer(wxHORIZONTAL, this, L"Layer 3 (Bandages / Colour)");
pLayer3->Add(new wxCheckBox(this, ID(3, 0), L"0"), 0, wxALIGN_CENTER | wxALL,
1);
pLayer3->Add(new wxCheckBox(this, ID(3, 2), L"2 (? / Yellow)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer3->Add(new wxCheckBox(this, ID(3, 4), L"4 (L Foot / Blue)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer3->Add(new wxCheckBox(this, ID(3, 6), L"6 (? / White)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer3->Add(new wxCheckBox(this, ID(3, 8), L"8 (R Arm)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer3->Add(new wxCheckBox(this, ID(3, 10), L"10 (R Foot)"), 0,
wxALIGN_CENTER | wxALL, 1);
pSidebarSizer->Add(pLayer3, 0, wxEXPAND | wxALL, 0);
pLayer3->Add(new wxCheckBox(this, make_id(3, 0), L"0"), layerSizerFlags);
pLayer3->Add(new wxCheckBox(this, make_id(3, 2), L"2 (? / Yellow)"),
layerSizerFlags);
pLayer3->Add(new wxCheckBox(this, make_id(3, 4), L"4 (L Foot / Blue)"),
layerSizerFlags);
pLayer3->Add(new wxCheckBox(this, make_id(3, 6), L"6 (? / White)"),
layerSizerFlags);
pLayer3->Add(new wxCheckBox(this, make_id(3, 8), L"8 (R Arm)"),
layerSizerFlags);
pLayer3->Add(new wxCheckBox(this, make_id(3, 10), L"10 (R Foot)"),
layerSizerFlags);
pSidebarSizer->Add(pLayer3, fillSizerFlags);
wxStaticBoxSizer* pLayer4 =
new wxStaticBoxSizer(wxHORIZONTAL, this, L"Layer 4 (Bandages / Repair)");
pLayer4->Add(new wxCheckBox(this, ID(4, 0), L"0"), 0, wxALIGN_CENTER | wxALL,
1);
pLayer4->Add(new wxCheckBox(this, ID(4, 2), L"2 (Head / Repair)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer4->Add(new wxCheckBox(this, ID(4, 4), L"4 (L Root)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer4->Add(new wxCheckBox(this, ID(4, 6), L"6"), 0, wxALIGN_CENTER | wxALL,
1);
pLayer4->Add(new wxCheckBox(this, ID(4, 8), L"8 (R Arm)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer4->Add(new wxCheckBox(this, ID(4, 10), L"10 (R Foot)"), 0,
wxALIGN_CENTER | wxALL, 1);
pSidebarSizer->Add(pLayer4, 0, wxEXPAND | wxALL, 0);
pLayer4->Add(new wxCheckBox(this, make_id(4, 0), L"0"), layerSizerFlags);
pLayer4->Add(new wxCheckBox(this, make_id(4, 2), L"2 (Head / Repair)"),
layerSizerFlags);
pLayer4->Add(new wxCheckBox(this, make_id(4, 4), L"4 (L Root)"),
layerSizerFlags);
pLayer4->Add(new wxCheckBox(this, make_id(4, 6), L"6"), layerSizerFlags);
pLayer4->Add(new wxCheckBox(this, make_id(4, 8), L"8 (R Arm)"),
layerSizerFlags);
pLayer4->Add(new wxCheckBox(this, make_id(4, 10), L"10 (R Foot)"),
layerSizerFlags);
pSidebarSizer->Add(pLayer4, fillSizerFlags);
wxStaticBoxSizer* pLayer5 =
new wxStaticBoxSizer(wxHORIZONTAL, this, L"Layer 5 (Staff Head)");
pLayer5->Add(new wxCheckBox(this, ID(5, 0), L"0"), 0, wxALIGN_CENTER | wxALL,
1);
pLayer5->Add(new wxCheckBox(this, ID(5, 2), L"2 (W1)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer5->Add(new wxCheckBox(this, ID(5, 4), L"4 (B1)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer5->Add(new wxCheckBox(this, ID(5, 6), L"6 (W2)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer5->Add(new wxCheckBox(this, ID(5, 8), L"8 (B2)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer5->Add(new wxCheckBox(this, ID(5, 10), L"10"), 0,
wxALIGN_CENTER | wxALL, 1);
pSidebarSizer->Add(pLayer5, 0, wxEXPAND | wxALL, 0);
pLayer5->Add(new wxCheckBox(this, make_id(5, 0), L"0"), layerSizerFlags);
pLayer5->Add(new wxCheckBox(this, make_id(5, 2), L"2 (W1)"), layerSizerFlags);
pLayer5->Add(new wxCheckBox(this, make_id(5, 4), L"4 (B1)"), layerSizerFlags);
pLayer5->Add(new wxCheckBox(this, make_id(5, 6), L"6 (W2)"), layerSizerFlags);
pLayer5->Add(new wxCheckBox(this, make_id(5, 8), L"8 (B2)"), layerSizerFlags);
pLayer5->Add(new wxCheckBox(this, make_id(5, 10), L"10"), layerSizerFlags);
pSidebarSizer->Add(pLayer5, fillSizerFlags);
wxStaticBoxSizer* pLayer10 = new wxStaticBoxSizer(
wxHORIZONTAL, this, L"Layer 10 (Wall Colour / Smoke)");
pLayer10->Add(new wxCheckBox(this, ID(10, 2), L"2 (Yellow / Smoke)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer10->Add(new wxCheckBox(this, ID(10, 4), L"4 (Blue)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer10->Add(new wxCheckBox(this, ID(10, 6), L"6 (White)"), 0,
wxALIGN_CENTER | wxALL, 1);
pSidebarSizer->Add(pLayer10, 0, wxEXPAND | wxALL, 0);
pLayer10->Add(new wxCheckBox(this, make_id(10, 2), L"2 (Yellow / Smoke)"),
layerSizerFlags);
pLayer10->Add(new wxCheckBox(this, make_id(10, 4), L"4 (Blue)"),
layerSizerFlags);
pLayer10->Add(new wxCheckBox(this, make_id(10, 6), L"6 (White)"),
layerSizerFlags);
pSidebarSizer->Add(pLayer10, fillSizerFlags);
wxStaticBoxSizer* pLayer11 = new wxStaticBoxSizer(
wxHORIZONTAL, this, L"Layer 11 (Wall Colour / Smoke / Screen)");
pLayer11->Add(new wxCheckBox(this, ID(11, 2), L"2 (Yellow / Smoke / On)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer11->Add(new wxCheckBox(this, ID(11, 4), L"4 (Blue)"), 0,
wxALIGN_CENTER | wxALL, 1);
pLayer11->Add(new wxCheckBox(this, ID(11, 6), L"6 (Green)"), 0,
wxALIGN_CENTER | wxALL, 1);
pSidebarSizer->Add(pLayer11, 0, wxEXPAND | wxALL, 0);
pLayer11->Add(
new wxCheckBox(this, make_id(11, 2), L"2 (Yellow / Smoke / On)"),
layerSizerFlags);
pLayer11->Add(new wxCheckBox(this, make_id(11, 4), L"4 (Blue)"),
layerSizerFlags);
pLayer11->Add(new wxCheckBox(this, make_id(11, 6), L"6 (Green)"),
layerSizerFlags);
pSidebarSizer->Add(pLayer11, fillSizerFlags);
wxStaticBoxSizer* pLayer12 =
new wxStaticBoxSizer(wxHORIZONTAL, this, L"Layer 12 (Smoke)");
pLayer12->Add(new wxCheckBox(this, ID(12, 2), L"2 (Smoke)"), 0,
wxALIGN_CENTER | wxALL, 1);
pSidebarSizer->Add(pLayer12, 0, wxEXPAND | wxALL, 0);
pLayer12->Add(new wxCheckBox(this, make_id(12, 2), L"2 (Smoke)"),
layerSizerFlags);
pSidebarSizer->Add(pLayer12, fillSizerFlags);
wxStaticBoxSizer* pMoodOverlay =
new wxStaticBoxSizer(wxVERTICAL, this, L"Overlays");
pMoodOverlay->Add(new wxCheckBox(this, ID_DRAW_MOOD, L"Draw mood overlay"), 0,
wxEXPAND | wxALL, 1);
pMoodOverlay->Add(new wxCheckBox(this, ID_DRAW_MOOD, L"Draw mood overlay"),
fillBorderSizerFlags);
wxBoxSizer* pMoodRow = new wxBoxSizer(wxHORIZONTAL);
pMoodRow->Add(
new wxStaticText(this, wxID_ANY, L"Marker position (click to move it):"),
0, wxEXPAND | wxRIGHT, 2);
wxSizerFlags(0).Expand().Border(wxRIGHT, 2));
pMoodRow->Add(
m_txtMoodPosition[0] = new wxTextCtrl(this, wxID_ANY, L"{0, 0}"), 1,
wxEXPAND | wxRIGHT, 1);
m_txtMoodPosition[0] = new wxTextCtrl(this, wxID_ANY, L"{0, 0}"),
wxSizerFlags(1).Expand().Border(wxRIGHT, 1));
pMoodRow->Add(
m_txtMoodPosition[1] = new wxTextCtrl(this, wxID_ANY, L"{0, 0, \"px\"}"),
1, wxEXPAND);
pMoodOverlay->Add(pMoodRow, 1, wxEXPAND | wxALL, 2);
wxSizerFlags(1).Expand());
pMoodOverlay->Add(pMoodRow, wxSizerFlags(1).Expand().Border(wxALL, 2));
pMoodOverlay->Add(
new wxCheckBox(this, ID_DRAW_COORDINATES, L"Draw tile coordinates"), 0,
wxEXPAND | wxALL, 0);
pSidebarSizer->Add(pMoodOverlay, 0, wxEXPAND | wxALL, 0);
new wxCheckBox(this, ID_DRAW_COORDINATES, L"Draw tile coordinates"),
fillSizerFlags);
pSidebarSizer->Add(pMoodOverlay, fillSizerFlags);
m_bDrawMood = false;
m_bDrawCoordinates = false;
m_iMoodDrawX = 0;
m_iMoodDrawY = 0;
for (int iLayer = 0; iLayer < 13; ++iLayer) {
wxCheckBox* pCheck = wxDynamicCast(FindWindow(ID(iLayer, 0)), wxCheckBox);
wxCheckBox* pCheck =
wxDynamicCast(FindWindow(make_id(iLayer, 0)), wxCheckBox);
if (pCheck != nullptr) {
pCheck->SetValue(true);
m_mskLayers.set(iLayer, 0);
}
}
Connect(ID(0, 0), ID(12, 24), wxEVT_COMMAND_CHECKBOX_CLICKED,
Connect(make_id(0, 0), make_id(12, 24), wxEVT_COMMAND_CHECKBOX_CLICKED,
(wxObjectEventFunction)&frmMain::_onToggleMask);
#undef ID
wxStaticBoxSizer* pSearch = new wxStaticBoxSizer(wxVERTICAL, this, L"Search");
wxBoxSizer* pSearchButtons = new wxBoxSizer(wxHORIZONTAL);
pSearchButtons->Add(new wxButton(this, ID_SEARCH_LAYER_ID, L"Layer/ID"), 0,
wxALL, 1);
pSearchButtons->Add(new wxButton(this, ID_SEARCH_FRAME, L"Frame"), 0, wxALL,
1);
pSearchButtons->Add(new wxButton(this, ID_SEARCH_SOUND, L"Sound"), 0, wxALL,
1);
pSearchButtons->Add(new wxButton(this, ID_SEARCH_LAYER_ID, L"Layer/ID"),
buttonSizerFlags);
pSearchButtons->Add(new wxButton(this, ID_SEARCH_FRAME, L"Frame"),
buttonSizerFlags);
pSearchButtons->Add(new wxButton(this, ID_SEARCH_SOUND, L"Sound"),
buttonSizerFlags);
pSearch->Add(pSearchButtons, 0);
pSearch->Add(m_lstSearchResults = new wxListBox(this, ID_SEARCH_RESULTS), 1,
wxEXPAND | wxALL, 1);
m_lstSearchResults = new wxListBox(this, ID_SEARCH_RESULTS);
pSearch->Add(m_lstSearchResults, wxSizerFlags(1).Expand().Border(wxALL, 1));
wxStaticBoxSizer* pFrameFlags =
new wxStaticBoxSizer(wxHORIZONTAL, this, L"Frame Flags");
wxSizerFlags frameSizerFlags(0);
frameSizerFlags.Expand().Border(wxALL, 2);
wxBoxSizer* pFlags1 = new wxBoxSizer(wxVERTICAL);
pFlags1->Add(m_txtFrameFlags[0] = new wxTextCtrl(this, wxID_ANY), 0,
wxEXPAND | wxALL, 2);
pFlags1->Add(m_chkFrameFlags[0] = new wxCheckBox(this, wxID_ANY, L"2^0"), 0,
wxEXPAND | wxALL, 2);
pFlags1->Add(m_chkFrameFlags[1] = new wxCheckBox(this, wxID_ANY, L"2^1"), 0,
wxEXPAND | wxALL, 2);
pFlags1->Add(m_chkFrameFlags[2] = new wxCheckBox(this, wxID_ANY, L"2^2"), 0,
wxEXPAND | wxALL, 2);
pFlags1->Add(m_chkFrameFlags[3] = new wxCheckBox(this, wxID_ANY, L"2^3"), 0,
wxEXPAND | wxALL, 2);
pFlags1->Add(m_chkFrameFlags[4] = new wxCheckBox(this, wxID_ANY, L"2^4"), 0,
wxEXPAND | wxALL, 2);
pFlags1->Add(m_chkFrameFlags[5] = new wxCheckBox(this, wxID_ANY, L"2^5"), 0,
wxEXPAND | wxALL, 2);
pFlags1->Add(m_chkFrameFlags[6] = new wxCheckBox(this, wxID_ANY, L"2^6"), 0,
wxEXPAND | wxALL, 2);
pFlags1->Add(m_chkFrameFlags[7] = new wxCheckBox(this, wxID_ANY, L"2^7"), 0,
wxEXPAND | wxALL, 2);
pFrameFlags->Add(pFlags1, 1, wxEXPAND);
m_txtFrameFlags[0] = new wxTextCtrl(this, wxID_ANY);
pFlags1->Add(m_txtFrameFlags[0], frameSizerFlags);
m_chkFrameFlags[0] = new wxCheckBox(this, wxID_ANY, L"2^0");
m_chkFrameFlags[1] = new wxCheckBox(this, wxID_ANY, L"2^1");
m_chkFrameFlags[2] = new wxCheckBox(this, wxID_ANY, L"2^2");
m_chkFrameFlags[3] = new wxCheckBox(this, wxID_ANY, L"2^3");
m_chkFrameFlags[4] = new wxCheckBox(this, wxID_ANY, L"2^4");
m_chkFrameFlags[5] = new wxCheckBox(this, wxID_ANY, L"2^5");
m_chkFrameFlags[6] = new wxCheckBox(this, wxID_ANY, L"2^6");
m_chkFrameFlags[7] = new wxCheckBox(this, wxID_ANY, L"2^7");
pFlags1->Add(m_chkFrameFlags[0], frameSizerFlags);
pFlags1->Add(m_chkFrameFlags[1], frameSizerFlags);
pFlags1->Add(m_chkFrameFlags[2], frameSizerFlags);
pFlags1->Add(m_chkFrameFlags[3], frameSizerFlags);
pFlags1->Add(m_chkFrameFlags[4], frameSizerFlags);
pFlags1->Add(m_chkFrameFlags[5], frameSizerFlags);
pFlags1->Add(m_chkFrameFlags[6], frameSizerFlags);
pFlags1->Add(m_chkFrameFlags[7], frameSizerFlags);
pFrameFlags->Add(pFlags1, wxSizerFlags(1).Expand());
wxBoxSizer* pFlags2 = new wxBoxSizer(wxVERTICAL);
pFlags2->Add(m_txtFrameFlags[1] = new wxTextCtrl(this, wxID_ANY), 0,
wxEXPAND | wxALL, 2);
pFlags2->Add(m_chkFrameFlags[8] =
new wxCheckBox(this, wxID_ANY, L"2^8 (Animation Start)"),
0, wxEXPAND | wxALL, 2);
pFlags2->Add(m_chkFrameFlags[9] = new wxCheckBox(this, wxID_ANY, L"2^9"), 0,
wxEXPAND | wxALL, 2);
pFlags2->Add(m_chkFrameFlags[10] = new wxCheckBox(this, wxID_ANY, L"2^10"), 0,
wxEXPAND | wxALL, 2);
pFlags2->Add(m_chkFrameFlags[11] = new wxCheckBox(this, wxID_ANY, L"2^11"), 0,
wxEXPAND | wxALL, 2);
pFlags2->Add(m_chkFrameFlags[12] = new wxCheckBox(this, wxID_ANY, L"2^12"), 0,
wxEXPAND | wxALL, 2);
pFlags2->Add(m_chkFrameFlags[13] = new wxCheckBox(this, wxID_ANY, L"2^13"), 0,
wxEXPAND | wxALL, 2);
pFlags2->Add(m_chkFrameFlags[14] = new wxCheckBox(this, wxID_ANY, L"2^14"), 0,
wxEXPAND | wxALL, 2);
pFlags2->Add(m_chkFrameFlags[15] = new wxCheckBox(this, wxID_ANY, L"2^15"), 0,
wxEXPAND | wxALL, 2);
pFrameFlags->Add(pFlags2, 1, wxEXPAND);
pMainSizer->Add(pSidebarSizer, 0, wxEXPAND | wxALL, 2);
m_txtFrameFlags[1] = new wxTextCtrl(this, wxID_ANY);
pFlags2->Add(m_txtFrameFlags[1], frameSizerFlags);
m_chkFrameFlags[8] = new wxCheckBox(this, wxID_ANY, L"2^8 (Animation Start)");
m_chkFrameFlags[9] = new wxCheckBox(this, wxID_ANY, L"2^9");
m_chkFrameFlags[10] = new wxCheckBox(this, wxID_ANY, L"2^10");
m_chkFrameFlags[11] = new wxCheckBox(this, wxID_ANY, L"2^11");
m_chkFrameFlags[12] = new wxCheckBox(this, wxID_ANY, L"2^12");
m_chkFrameFlags[13] = new wxCheckBox(this, wxID_ANY, L"2^13");
m_chkFrameFlags[14] = new wxCheckBox(this, wxID_ANY, L"2^14");
m_chkFrameFlags[15] = new wxCheckBox(this, wxID_ANY, L"2^15");
pFlags2->Add(m_chkFrameFlags[8], frameSizerFlags);
pFlags2->Add(m_chkFrameFlags[9], frameSizerFlags);
pFlags2->Add(m_chkFrameFlags[10], frameSizerFlags);
pFlags2->Add(m_chkFrameFlags[11], frameSizerFlags);
pFlags2->Add(m_chkFrameFlags[12], frameSizerFlags);
pFlags2->Add(m_chkFrameFlags[13], frameSizerFlags);
pFlags2->Add(m_chkFrameFlags[14], frameSizerFlags);
pFlags2->Add(m_chkFrameFlags[15], frameSizerFlags);
pFrameFlags->Add(pFlags2, wxSizerFlags(1).Expand());
pMainSizer->Add(pSidebarSizer, wxSizerFlags(0).Expand().Border(wxALL, 2));
wxSizer* pRightHandSizer = new wxBoxSizer(wxVERTICAL);
pRightHandSizer->AddSpacer(1);
pRightHandSizer->Add(
m_panFrame = new wxPanel(this, wxID_ANY, def, wxBORDER_SIMPLE), 0,
wxEXPAND | wxALL, 2);
m_panFrame = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxBORDER_SIMPLE);
pRightHandSizer->Add(m_panFrame, wxSizerFlags(0).Expand().Border(wxALL, 2));
m_panFrame->Connect(wxEVT_PAINT,
(wxObjectEventFunction)&frmMain::_onPanelPaint, nullptr,
this);
@@ -378,11 +388,10 @@ frmMain::frmMain()
m_panFrame->SetMinSize(m_panFrame->ClientToWindowSize(wxSize(402, 402)));
pRightHandSizer->AddSpacer(1);
pRightHandSizer->Add(pSearch, 1, wxEXPAND | wxALL, 0);
pRightHandSizer->Add(pFrameFlags, 0, wxEXPAND | wxALL, 0);
pMainSizer->Add(pRightHandSizer, 1, wxEXPAND | wxALL, 0);
pRightHandSizer->Add(pSearch, wxSizerFlags(1).Expand().Border(wxALL, 0));
pRightHandSizer->Add(pFrameFlags, fillSizerFlags);
pMainSizer->Add(pRightHandSizer, wxSizerFlags(1).Expand().Border(wxALL, 0));
SetBackgroundColour(m_btnPlayPause->GetBackgroundColour());
SetSizer(pMainSizer);
SetMinSize(ClientToWindowSize(pMainSizer->CalcMin()));
@@ -444,7 +453,7 @@ void frmMain::load() {
m_oAnims.markDuplicates();
m_txtAnimCount->SetValue(
wxString::Format(L"%u", (int)m_oAnims.getAnimationCount()));
wxString::Format(L"%zu", m_oAnims.getAnimationCount()));
m_imgBackground.Create(400, 400);
{
@@ -522,11 +531,13 @@ void frmMain::_onLastAnim(wxCommandEvent& evt) {
}
void frmMain::_onAnimChar(wxCommandEvent& evt) {
long iAnim;
if (evt.GetString().ToLong(&iAnim)) {
if (iAnim >= 0 && iAnim < (long)m_oAnims.getAnimationCount()) {
_onAnimChange((size_t)iAnim);
long conv;
if (evt.GetString().ToLong(&conv)) {
if (conv < 0 || conv >= m_oAnims.getAnimationCount()) {
return;
}
size_t iAnim = static_cast<size_t>(conv);
_onAnimChange(iAnim);
}
}
@@ -544,7 +555,7 @@ void frmMain::_onGhostIndexChange(wxSpinEvent& evt) {
void frmMain::_onAnimChange(size_t iIndex) {
m_iCurrentAnim = iIndex;
m_txtAnimIndex->ChangeValue(wxString::Format(L"%u", (int)iIndex));
m_txtAnimIndex->ChangeValue(wxString::Format(L"%zu", iIndex));
m_iCurrentFrame = 0;
THLayerMask oMask;
@@ -562,7 +573,7 @@ void frmMain::_onAnimChange(size_t iIndex) {
m_panFrame->Refresh(false);
m_txtFrameIndex->SetValue(wxString::Format(L"0"));
m_txtFrameCount->SetValue(
wxString::Format(L"%u", (int)m_oAnims.getFrameCount(iIndex)));
wxString::Format(L"%zu", m_oAnims.getFrameCount(iIndex)));
}
void frmMain::_onPlayPause(wxCommandEvent& evt) {
@@ -581,7 +592,7 @@ void frmMain::_onPrevFrame(wxCommandEvent& evt) {
else
m_iCurrentFrame =
(m_iCurrentFrame - 1) % m_oAnims.getFrameCount(m_iCurrentAnim);
m_txtFrameIndex->SetValue(wxString::Format(L"%u", m_iCurrentFrame));
m_txtFrameIndex->SetValue(wxString::Format(L"%zu", m_iCurrentFrame));
m_panFrame->Refresh(false);
}
@@ -590,7 +601,7 @@ void frmMain::_onNextFrame(wxCommandEvent& evt) {
m_iCurrentFrame =
(m_iCurrentFrame + 1) % m_oAnims.getFrameCount(m_iCurrentAnim);
m_txtFrameIndex->SetValue(wxString::Format(L"%u", m_iCurrentFrame));
m_txtFrameIndex->SetValue(wxString::Format(L"%zu", m_iCurrentFrame));
m_panFrame->Refresh(false);
}
@@ -600,7 +611,7 @@ void frmMain::_onTimer(wxTimerEvent& evt) {
m_iCurrentFrame =
(m_iCurrentFrame + 1) % m_oAnims.getFrameCount(m_iCurrentAnim);
m_txtFrameIndex->SetValue(wxString::Format(L"%u", (int)m_iCurrentFrame));
m_txtFrameIndex->SetValue(wxString::Format(L"%zu", m_iCurrentFrame));
m_panFrame->Refresh(false);
}
}
@@ -687,14 +698,17 @@ void frmMain::_onPanelClick(wxMouseEvent& evt) {
}
void frmMain::_onSearchLayerId(wxCommandEvent& evt) {
long iLayer = ::wxGetNumberFromUser(
long input = ::wxGetNumberFromUser(
L"Enter the layer number to search in (0 - 12)", L"Layer:",
L"Search for Layer / ID Combo", 0, 0, 13, this);
if (iLayer < 0 || iLayer > 12) return;
long iID = ::wxGetNumberFromUser(
L"Enter the ID number to search for (0 - 24)", L"ID:",
L"Search for Layer / ID Combo", 0, 0, 24, this);
if (iID < 0 || iID > 24) return;
if (input < 0 || input > 12) return;
int iLayer = static_cast<int>(input);
input = ::wxGetNumberFromUser(L"Enter the ID number to search for (0 - 24)",
L"ID:", L"Search for Layer / ID Combo", 0, 0,
24, this);
if (input < 0 || input > 24) return;
int iID = static_cast<int>(input);
m_lstSearchResults->Clear();
wxBusyCursor oBusy;
@@ -703,33 +717,35 @@ void frmMain::_onSearchLayerId(wxCommandEvent& evt) {
THLayerMask mskAnim;
m_oAnims.getAnimationMask(i, mskAnim);
if (mskAnim.isSet(static_cast<int>(iLayer), static_cast<int>(iID))) {
m_lstSearchResults->Append(wxString::Format(L"%i", (int)i));
if (mskAnim.isSet(iLayer, iID)) {
m_lstSearchResults->Append(wxString::Format(L"%zu", i));
}
}
}
void frmMain::_onSearchFrame(wxCommandEvent& evt) {
long iFrame =
long input =
::wxGetNumberFromUser(L"Enter the frame number to search for.", L"Frame:",
L"Search for frame", 0, 0, 20000, this);
if (iFrame == -1) return;
if (input == -1) return;
size_t iFrame = static_cast<size_t>(input);
m_lstSearchResults->Clear();
wxBusyCursor oBusy;
for (size_t i = 0; i < m_oAnims.getAnimationCount(); ++i) {
if (m_oAnims.isAnimationDuplicate(i)) continue;
if (m_oAnims.doesAnimationIncludeFrame(i, iFrame)) {
m_lstSearchResults->Append(wxString::Format(L"%i", (int)i));
m_lstSearchResults->Append(wxString::Format(L"%zu", i));
}
}
}
void frmMain::_onSearchSoundIndex(wxCommandEvent& evt) {
long iFrame = ::wxGetNumberFromUser(L"Enter the sound index to search for.",
L"Sound index:", L"Search for sound", 0,
0, 256, this);
if (iFrame == -1) return;
long input = ::wxGetNumberFromUser(L"Enter the sound index to search for.",
L"Sound index:", L"Search for sound", 0, 0,
256, this);
if (input == -1) return;
size_t iFrame = static_cast<size_t>(input);
m_lstSearchResults->Clear();
wxBusyCursor oBusy;
@@ -738,19 +754,13 @@ void frmMain::_onSearchSoundIndex(wxCommandEvent& evt) {
size_t iCount = m_oAnims.getFrameCount(i);
for (size_t j = 0; j < iCount; ++j) {
if ((m_oAnims.getFrameStruct(i, j)->flags & 0xFF) == iFrame) {
m_lstSearchResults->Append(wxString::Format(L"%i", (int)i));
m_lstSearchResults->Append(wxString::Format(L"%zu", i));
break;
}
}
}
}
void frmMain::_onGotoSearchResult(wxCommandEvent& evt) {
long iAnim;
evt.GetString().ToLong(&iAnim);
_onAnimChange(iAnim);
}
void frmMain::_drawCoordinates(wxPaintDC& DC, int i, int j) {
int x = 122; // tile (0, 0) text start x-coordinate
int y = 226; // tile (0, 0) text start y-coordinate
@@ -761,9 +771,6 @@ void frmMain::_drawCoordinates(wxPaintDC& DC, int i, int j) {
wxString frmMain::_getCaseSensitivePath(const wxString& sInsensitivePathPart,
const wxString& sPath) {
bool found;
bool cont;
if (!wxFileName::IsCaseSensitive()) {
return sPath + sInsensitivePathPart;
}
@@ -781,9 +788,11 @@ wxString frmMain::_getCaseSensitivePath(const wxString& sInsensitivePathPart,
wxString pathPart = pathTokenizer.GetNextToken();
wxString realName;
cont = dir.GetFirst(&realName, wxEmptyString,
wxDIR_DIRS | wxDIR_FILES | wxDIR_HIDDEN | wxDIR_DOTDOT);
found = false;
bool cont =
dir.GetFirst(&realName, wxEmptyString,
wxDIR_DIRS | wxDIR_FILES | wxDIR_HIDDEN | wxDIR_DOTDOT);
bool found = false;
while (cont) {
if (realName.Upper() == pathPart.Upper()) {
if (retStr.Last() != wxFileName::GetPathSeparator()) {

View File

@@ -48,43 +48,46 @@ frmSprites::frmSprites()
wxStaticBoxSizer* pFiles = new wxStaticBoxSizer(wxVERTICAL, this, L"Files");
wxFlexGridSizer* pFilesGrid = new wxFlexGridSizer(4, 3, 2, 1);
pFilesGrid->AddGrowableCol(1, 1);
pFilesGrid->Add(new wxStaticText(this, wxID_ANY, L"Table:"), 0,
wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT);
pFilesGrid->Add(
m_txtTable = new wxTextCtrl(
this, wxID_ANY, L"X:\\ThemeHospital\\hospital\\QData\\Font00V.tab"),
1, wxALIGN_CENTER_VERTICAL | wxEXPAND);
pFilesGrid->Add(new wxButton(this, ID_BROWSE_TABLE, L"Browse..."), 0,
wxALIGN_CENTER_VERTICAL);
pFilesGrid->Add(new wxStaticText(this, wxID_ANY, L"Data:"), 0,
wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT);
pFilesGrid->Add(m_txtData = new wxTextCtrl(this, wxID_ANY, L""), 1,
wxALIGN_CENTER_VERTICAL | wxEXPAND);
pFilesGrid->Add(new wxButton(this, ID_BROWSE_DATA, L"Browse..."), 0,
wxALIGN_CENTER_VERTICAL);
pFilesGrid->Add(new wxStaticText(this, wxID_ANY, L"Palette:"), 0,
wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT);
pFilesGrid->Add(
m_txtPalette = new wxTextCtrl(
this, wxID_ANY, L"X:\\ThemeHospital\\hospital\\Data\\MPalette.dat"),
1, wxALIGN_CENTER_VERTICAL | wxEXPAND);
pFilesGrid->Add(new wxStaticText(this, wxID_ANY, L"Table:"),
wxSizerFlags(0).Center().Right());
m_txtTable = new wxTextCtrl(
this, wxID_ANY, L"X:\\ThemeHospital\\hospital\\QData\\Font00V.tab");
pFilesGrid->Add(m_txtTable,
wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL).Expand());
pFilesGrid->Add(new wxButton(this, ID_BROWSE_TABLE, L"Browse..."),
wxSizerFlags(0).Align(wxALIGN_CENTER_VERTICAL));
pFilesGrid->Add(new wxStaticText(this, wxID_ANY, L"Data:"),
wxSizerFlags(0).Center().Right());
m_txtData = new wxTextCtrl(this, wxID_ANY, L"");
pFilesGrid->Add(m_txtData,
wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL).Expand());
pFilesGrid->Add(new wxButton(this, ID_BROWSE_DATA, L"Browse..."),
wxSizerFlags(0).Align(wxALIGN_CENTER_VERTICAL));
pFilesGrid->Add(new wxStaticText(this, wxID_ANY, L"Palette:"),
wxSizerFlags(0).Center().Right());
m_txtPalette = new wxTextCtrl(
this, wxID_ANY, L"X:\\ThemeHospital\\hospital\\Data\\MPalette.dat");
pFilesGrid->Add(m_txtPalette,
wxSizerFlags(1).Align(wxALIGN_CENTER_VERTICAL).Expand());
pFilesGrid->Add(new wxButton(this, ID_BROWSE_PALETTE, L"Browse..."), 0,
wxALIGN_CENTER_VERTICAL);
pFiles->Add(pFilesGrid, 0, wxEXPAND | wxALL, 1);
wxButton* pTmp;
pFiles->Add(pTmp = new wxButton(this, ID_LOAD, L"Load Simple"), 0,
wxALIGN_CENTER | wxALL, 1);
pFiles->Add(pTmp = new wxButton(this, ID_LOAD_COMPLEX, L"Load Complex"), 0,
wxALIGN_CENTER | wxALL, 1);
pFiles->Add(pTmp = new wxButton(this, ID_NEXT, L"Next"), 0,
wxALIGN_CENTER | wxALL, 1);
SetBackgroundColour(pTmp->GetBackgroundColour());
pMainSizer->Add(pFiles, 0, wxEXPAND | wxALL, 2);
pFiles->Add(pFilesGrid, wxSizerFlags(0).Expand().Border(wxALL, 1));
pFiles->Add(new wxButton(this, ID_LOAD, L"Load Simple"),
wxSizerFlags(0).Center().Border(wxALL, 1));
pFiles->Add(new wxButton(this, ID_LOAD_COMPLEX, L"Load Complex"),
wxSizerFlags(0).Center().Border(wxALL, 1));
pFiles->Add(new wxButton(this, ID_NEXT, L"Next"),
wxSizerFlags(0).Center().Border(wxALL, 1));
pMainSizer->Add(pFiles, wxSizerFlags(0).Expand().Border(wxALL, 2));
wxStaticBoxSizer* pSprites =
new wxStaticBoxSizer(wxVERTICAL, this, L"Sprites");
pSprites->Add(m_panFrame = new MyVScrolled(this), 1, wxEXPAND);
pMainSizer->Add(pSprites, 1, wxEXPAND | wxALL, 2);
m_panFrame = new MyVScrolled(this);
pSprites->Add(m_panFrame, wxSizerFlags(1).Expand());
pMainSizer->Add(pSprites, wxSizerFlags(1).Expand().Border(wxALL, 2));
m_panFrame->Connect(wxEVT_PAINT,
(wxObjectEventFunction)&frmSprites::_onPanelPaint,
nullptr, this);

View File

@@ -36,7 +36,9 @@ SOFTWARE.
#include "../libs/rnc/rnc.h"
static const unsigned char palette_upscale_map[0x40] = {
namespace {
constexpr unsigned char palette_upscale_map[0x40] = {
0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, 0x20, 0x24, 0x28,
0x2D, 0x31, 0x35, 0x39, 0x3D, 0x41, 0x45, 0x49, 0x4D, 0x51, 0x55,
0x59, 0x5D, 0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D, 0x82,
@@ -115,8 +117,8 @@ class ChunkRenderer {
bool m_skip_eol;
};
static void decode_chunks(ChunkRenderer& renderer, const unsigned char* data,
size_t datalen, unsigned char transparent) {
void decode_chunks(ChunkRenderer& renderer, const unsigned char* data,
size_t datalen, unsigned char transparent) {
while (!renderer.isDone() && datalen > 0) {
unsigned char b = *data;
--datalen;
@@ -136,9 +138,8 @@ static void decode_chunks(ChunkRenderer& renderer, const unsigned char* data,
renderer.chunkFinish(transparent);
}
static void decode_chunks_complex(ChunkRenderer& renderer,
const unsigned char* data, size_t datalen,
unsigned char transparent) {
void decode_chunks_complex(ChunkRenderer& renderer, const unsigned char* data,
size_t datalen, unsigned char transparent) {
while (!renderer.isDone() && datalen > 0) {
unsigned char b = *data;
--datalen;
@@ -178,6 +179,14 @@ static void decode_chunks_complex(ChunkRenderer& renderer,
renderer.chunkFinish(transparent);
}
void merge_colour(th_colour_t& dst, const th_colour_t& src) {
dst.r = (uint8_t)(((unsigned int)dst.r + (unsigned int)src.r) / 2);
dst.g = (uint8_t)(((unsigned int)dst.g + (unsigned int)src.g) / 2);
dst.b = (uint8_t)(((unsigned int)dst.b + (unsigned int)src.b) / 2);
}
} // namespace
THLayerMask::THLayerMask() { clear(); }
void THLayerMask::clear() {
@@ -458,12 +467,6 @@ void Bitmap::blit(Bitmap& bmpCanvas, int iX, int iY, int iFlags) const {
}
}
static inline void _merge(th_colour_t& dst, const th_colour_t& src) {
dst.r = (uint8_t)(((unsigned int)dst.r + (unsigned int)src.r) / 2);
dst.g = (uint8_t)(((unsigned int)dst.g + (unsigned int)src.g) / 2);
dst.b = (uint8_t)(((unsigned int)dst.b + (unsigned int)src.b) / 2);
}
void Bitmap::blit(wxImage& imgCanvas, int iX, int iY,
const unsigned char* pColourTranslate,
const th_colour_t* pPalette, int iFlags) const {
@@ -491,10 +494,10 @@ void Bitmap::blit(wxImage& imgCanvas, int iX, int iY,
th_colour_t dstc = pCanvas[iDstY * iCanvasWidth + iDstX];
switch (iFlags & 0xC) {
case 0x8:
_merge(srcc, dstc);
merge_colour(srcc, dstc);
// fall-through
case 0x4:
_merge(srcc, dstc);
merge_colour(srcc, dstc);
break;
}
}

View File

@@ -92,27 +92,26 @@ class THLayerMask {
inline void set(int iLayer, int iID) {
if (0 <= iLayer && iLayer < 13 && 0 <= iID && iID < 32)
m_iMask[iLayer] |= (1 << iID);
m_iMask[iLayer] |= (1ul << iID);
}
void clear();
inline void clear(int iLayer, int iID) {
if (0 <= iLayer && iLayer < 13 && 0 <= iID && iID < 32)
m_iMask[iLayer] &= ~(1 << iID);
m_iMask[iLayer] &= ~(1ul << iID);
}
inline bool isSet(int iLayer, int iID) const {
if (0 <= iLayer && iLayer < 13 && 0 <= iID && iID < 32)
return (m_iMask[iLayer] & (1 << iID)) != 0;
return (m_iMask[iLayer] & (1ul << iID)) != 0;
else
return false;
}
inline bool isSet(int iLayer) const {
if (0 <= iLayer && iLayer < 13)
for (int iId = 0; iId < 32; ++iId) {
if ((m_iMask[iLayer] & (static_cast<std::uint32_t>(1) << iId)) != 0)
return true;
if ((m_iMask[iLayer] & (1ul << iId)) != 0) return true;
}
return false;
}
@@ -216,7 +215,7 @@ class THAnimations {
wxFile oFile(sFilename);
if (!oFile.IsOpened()) return false;
size_t iLen = oFile.Length();
size_t iLen = static_cast<size_t>(oFile.Length());
unsigned char* pBuffer = new unsigned char[iLen];
oFile.Read(pBuffer, iLen);
if (memcmp(pBuffer, "RNC\001", 4) == 0) {

View File

@@ -5,14 +5,35 @@ add_custom_command(TARGET CorsixTH POST_BUILD
$<TARGET_FILE_DIR:CorsixTH>/socket
)
add_custom_command(TARGET CorsixTH POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${VCPKG_INSTALLED_PATH}/share/lua/ssl"
$<TARGET_FILE_DIR:CorsixTH>/ssl
)
add_custom_command(TARGET CorsixTH POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${VCPKG_INSTALLED_PATH}/share/lua/ltn12.lua"
"${VCPKG_INSTALLED_PATH}/share/lua/mime.lua"
"${VCPKG_INSTALLED_PATH}/share/lua/re.lua"
"${VCPKG_INSTALLED_PATH}/share/lua/socket.lua"
"${VCPKG_INSTALLED_PATH}/share/lua/ssl.lua"
"${VCPKG_INSTALLED_PATH}/$<$<CONFIG:Debug>:debug/>bin/lfs.dll"
"${VCPKG_INSTALLED_PATH}/$<$<CONFIG:Debug>:debug/>bin/lpeg.dll"
"${VCPKG_INSTALLED_PATH}/$<$<CONFIG:Debug>:debug/>bin/ssl.dll"
$<TARGET_FILE_DIR:CorsixTH>
)
if(${_VCPKG_TARGET_TRIPLET} STREQUAL "x64-windows")
set(_OPENSSL_SUFFIX "-x64")
else()
set(_OPENSSL_SUFFIX "")
endif()
add_custom_command(TARGET CorsixTH POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
"${VCPKG_INSTALLED_PATH}/$<$<CONFIG:Debug>:debug/>bin/libcrypto-1_1${_OPENSSL_SUFFIX}.dll"
"${VCPKG_INSTALLED_PATH}/$<$<CONFIG:Debug>:debug/>bin/libssl-1_1${_OPENSSL_SUFFIX}.dll"
$<TARGET_FILE_DIR:CorsixTH>
)

View File

@@ -30,7 +30,7 @@ set(_DEPS_GIT_URL "https://github.com/CorsixTH/deps.git")
# Select the optimal dependencies commit regardless where master is.
set(_DEPS_GIT_SHA "a23eb28bb8998b93215eccf805ee5462d75a57f2")
ExternalProject_Add(${_DEPS_PROJECT_NAME}
externalproject_add(${_DEPS_PROJECT_NAME}
PREFIX ${PRECOMPILED_DEPS_BASE_DIR}
GIT_REPOSITORY ${_DEPS_GIT_URL}
GIT_TAG ${_DEPS_GIT_SHA}

View File

@@ -18,7 +18,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
set(VCPKG_COMMIT_SHA "10ca4c5ba5218c14c4bf91570afe8bd5d63da387")
set(VCPKG_COMMIT_SHA "c2691026a5e5aad05d7e7d89d605ccb595cbbcb6")
# Setup the various paths we are using
set(_VCPKG_SCRIPT_NAME "build_vcpkg_deps.ps1")
@@ -63,4 +63,4 @@ if(err_val)
endif()
set(VCPKG_INSTALLED_PATH ${VCPKG_PARENT_DIR}/vcpkg/installed/${_VCPKG_TARGET_TRIPLET})
set(CMAKE_TOOLCHAIN_FILE ${VCPKG_PARENT_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake)
set(CMAKE_TOOLCHAIN_FILE ${VCPKG_PARENT_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file")

View File

@@ -1,19 +1,20 @@
# Cmake File for CorsixTH
# OPTIONS AVAILABLE:
# Any of the following: (default)
# - WITH_AUDIO : Activate sound (yes)
# - WITH_FREETYPE2 : Active support for non-Latin script alphabets (yes)
# - WITH_MOVIES : Activate movies (requires with_audio, FFmpeg) (yes)
# - WITH_LUAJIT : Use LuaJIT instead of Lua (must specify library path) (no)
# - WITH_LIBAV : Use Libav instead of FFmpeg when building movies (no)
# - WITH_AUDIO : Activate sound (yes)
# - WITH_FREETYPE2 : Active support for non-Latin script alphabets (yes)
# - WITH_MOVIES : Activate movies (requires with_audio, FFmpeg) (yes)
# - WITH_LUAJIT : Use LuaJIT instead of Lua (must specify library path) (no)
# - WITH_LIBAV : Use Libav instead of FFmpeg when building movies (no)
# - USE_SOURCE_DATADIRS : Use the source directory for loading resources. Incompatible with the install target (no)
# - WITH_VLD : Build with Visual Leak Detector (requires Visual Studio) (no)
# - WITH_VLD : Build with Visual Leak Detector (requires Visual Studio) (no)
# - USE_PRECOMPILED_DEPS : Use precompiled dependencies on *nix (no)
# - USE_VCPKG_DEPS : Build vcpkg dependencies locally (requires Visual Studio) (no)
# - WITH_LUAROCKS : Install required luarocks in the macOS app (requires luarocks)
# - ENABLE_UNIT_TESTS : Enable Unit Testing Target (requires Catch2) (no)
# - BUILD_ANIMVIEWER : Generate AnimViewer build files (no)
# - BUILD_ANIMVIEW : Generate AnimViewer build files (no)
cmake_minimum_required(VERSION 3.5)
@@ -53,8 +54,12 @@ if(UNIX AND CMAKE_COMPILER_IS_GNU)
option(USE_PRECOMPILED_DEPS "Use Precompiled Dependencies" OFF) # Make *nix systems opt in
endif()
if(APPLE)
option(WITH_LUAROCKS "Install required luarocks in the app" OFF)
endif()
option(ENABLE_UNIT_TESTS "Enables Unit Testing Targets" OFF)
option(BUILD_ANIMVIEWER "Build the animation viewer as part of the build process" OFF)
option(BUILD_ANIMVIEW "Build the animation viewer as part of the build process" OFF)
if(WITH_AUDIO)
set(CORSIX_TH_USE_SDL_MIXER ON)
@@ -124,7 +129,7 @@ add_subdirectory("libs")
message("Building CorsixTH")
add_subdirectory(CorsixTH)
if(BUILD_ANIMVIEWER)
if(BUILD_ANIMVIEW)
message("Building AnimView")
add_subdirectory(AnimView)
endif()

View File

@@ -1,7 +1,7 @@
# Sanity check
if(CORSIX_TH_DONE_TOP_LEVEL_CMAKE)
else()
message(FATAL_ERROR "Please run cmake on the top-level directory, not this one.")
message(FATAL_ERROR "Please run CMake from the top-level directory instead of here.")
endif()
# Project Declaration
@@ -26,8 +26,6 @@ else()
set(CORSIX_TH_INTERPRETER_PATH ${CMAKE_INSTALL_FULL_DATADIR}/corsix-th/CorsixTH.lua)
endif()
add_library(CorsixTH_lib STATIC)
# Declaration of the executable
if(APPLE)
set(corsixth_icon_file ${CMAKE_SOURCE_DIR}/CorsixTH/Icon.icns)
@@ -45,9 +43,25 @@ if(APPLE)
elseif(MSVC)
add_executable(CorsixTH CorsixTH.rc)
else()
add_executable(CorsixTH)
add_executable(CorsixTH "")
endif()
# Report operating system in compile_opts.os
if(WIN32)
set(CORSIX_TH_OS "windows")
elseif(APPLE)
set(CORSIX_TH_OS "macos")
else()
set(CORSIX_TH_OS "unix")
endif()
# Ensure config.h is picked up by cmake - moving this into subdir cmake files will
# prevent it applying to the CorsixTH project
set(CorsixTH_generated_src_dir ${CMAKE_BINARY_DIR}/CorsixTH/Src/)
add_library(CorsixTH_lib STATIC ${CorsixTH_generated_src_dir})
target_include_directories(CorsixTH_lib PUBLIC ${CorsixTH_generated_src_dir})
target_link_libraries(CorsixTH_lib RncLib)
target_link_libraries(CorsixTH CorsixTH_lib)
@@ -60,10 +74,6 @@ endif()
add_subdirectory(${PROJECT_SOURCE_DIR}/Src)
add_subdirectory(${PROJECT_SOURCE_DIR}/SrcUnshared)
# Ensure config.h is picked up by cmake - moving this into subdir cmake files will
# prevent it applying to the CorsixTH project
target_include_directories(CorsixTH_lib PUBLIC ${CMAKE_BINARY_DIR}/CorsixTH/Src/)
# Set language standard
set_property(TARGET CorsixTH_lib PROPERTY CXX_STANDARD 14)
set_property(TARGET CorsixTH_lib PROPERTY CXX_EXTENSIONS OFF)
@@ -77,7 +87,7 @@ set_property(TARGET CorsixTH PROPERTY CXX_STANDARD_REQUIRED ON)
if(USE_VCPKG_DEPS)
include(CopyVcpkgLua)
include(CopyVcpkgSdlMixerBackends)
ENDIF()
endif()
## Finding libraries
@@ -267,8 +277,8 @@ if(NOT USE_SOURCE_DATADIRS)
if(UNIX AND NOT APPLE)
install(FILES corsix-th.6 DESTINATION ${CMAKE_INSTALL_MANDIR}/man6)
install(FILES com.corsixth.CorsixTH.appdata.xml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo)
install(FILES com.corsixth.CorsixTH.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications)
install(FILES com.corsixth.corsixth.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo)
install(FILES com.corsixth.corsixth.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications)
install(FILES Original_Logo.svg DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps RENAME corsix-th.svg)
endif()
@@ -277,7 +287,12 @@ if(NOT USE_SOURCE_DATADIRS)
install(CODE "
INCLUDE(BundleUtilities)
SET(BU_CHMOD_BUNDLE_ITEMS ON)
FIXUP_BUNDLE(${CMAKE_INSTALL_PREFIX}/CorsixTH.app \"\" \"\")
FIXUP_BUNDLE(\"${CMAKE_INSTALL_PREFIX}/CorsixTH.app\" \"\" \"\")
")
if(WITH_LUAROCKS)
install(CODE "execute_process(
COMMAND bash \"${CMAKE_SOURCE_DIR}/scripts/macos_luarocks\" \"${CMAKE_INSTALL_PREFIX}\")
")
endif()
endif()
endif()

View File

@@ -9,6 +9,12 @@ if (package and package.preload and package.preload.TH) == nil then
error "This file must be invoked by the CorsixTH executable"
end
-- Set a large enough cstacklimit to load complex saves in stack based
-- versions of lua, such as 5.4.[01]
if debug.setcstacklimit then
debug.setcstacklimit(30000)
end
-- Parse script parameters:
local run_debugger = false
for _, arg in ipairs({...}) do
@@ -82,7 +88,7 @@ end
-- Check Lua version
if _VERSION ~= "Lua 5.1" then
if _VERSION == "Lua 5.2" or _VERSION == "Lua 5.3" then
if _VERSION == "Lua 5.2" or _VERSION == "Lua 5.3" or _VERSION == "Lua 5.4" then
-- Compatibility: Keep the global unpack function
unpack = table.unpack
-- Compatibility: Provide a replacement for deprecated ipairs()

View File

@@ -1,17 +1,17 @@
# MIT License
#
#
# Copyright (c) 2019 David Fairbrother
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -28,8 +28,11 @@ add_custom_target(AllTests)
include_directories(${CMAKE_SOURCE_DIR}/CorsixTH/Src/)
add_executable(UnitTests "")
target_link_libraries(UnitTests Catch2::Catch2)
target_link_libraries(UnitTests CorsixTH_lib)
target_link_libraries(UnitTests
Catch2::Catch2
CorsixTH_lib
${LUA_LIBRARY}
${CMAKE_DL_LIBS})
set_property(TARGET UnitTests PROPERTY CXX_STANDARD 14)
set_property(TARGET UnitTests PROPERTY CXX_EXTENSIONS OFF)
@@ -46,5 +49,9 @@ add_dependencies(AllTests UnitTests)
target_sources(UnitTests
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/test_main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/example.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_th_lua.cpp
)
include(CTest)
include(Catch)
catch_discover_tests(UnitTests)

View File

@@ -1,6 +0,0 @@
#include "catch2/catch.hpp"
TEST_CASE("Example Test", "[example]")
{
REQUIRE(true);
}

View File

@@ -0,0 +1,113 @@
#include "../Src/th_lua.h"
#include "catch2/catch.hpp"
TEST_CASE("luaT_rotate works", "[luaT_rotate]") {
lua_State* L = luaL_newstate();
lua_pushnumber(L, 1);
lua_pushnumber(L, 2);
lua_pushnumber(L, 3);
lua_pushnumber(L, 4);
lua_pushnumber(L, 5);
lua_pushnumber(L, 6);
lua_pushnumber(L, 7);
REQUIRE(lua_gettop(L) == 7);
SECTION("rotate 0") {
luaT_rotate(L, 1, 0);
REQUIRE(lua_tonumber(L, 1) == 1);
REQUIRE(lua_tonumber(L, 2) == 2);
REQUIRE(lua_tonumber(L, 3) == 3);
REQUIRE(lua_tonumber(L, 4) == 4);
REQUIRE(lua_tonumber(L, 5) == 5);
REQUIRE(lua_tonumber(L, 6) == 6);
REQUIRE(lua_tonumber(L, 7) == 7);
}
SECTION("rotate all up 1") {
luaT_rotate(L, 1, 1);
REQUIRE(lua_tonumber(L, 1) == 7);
REQUIRE(lua_tonumber(L, 2) == 1);
REQUIRE(lua_tonumber(L, 3) == 2);
REQUIRE(lua_tonumber(L, 4) == 3);
REQUIRE(lua_tonumber(L, 5) == 4);
REQUIRE(lua_tonumber(L, 6) == 5);
REQUIRE(lua_tonumber(L, 7) == 6);
}
SECTION("rotate all down 1") {
luaT_rotate(L, 1, -1);
REQUIRE(lua_tonumber(L, 1) == 2);
REQUIRE(lua_tonumber(L, 2) == 3);
REQUIRE(lua_tonumber(L, 3) == 4);
REQUIRE(lua_tonumber(L, 4) == 5);
REQUIRE(lua_tonumber(L, 5) == 6);
REQUIRE(lua_tonumber(L, 6) == 7);
REQUIRE(lua_tonumber(L, 7) == 1);
}
SECTION("rotate all up 2") {
luaT_rotate(L, 1, 2);
REQUIRE(lua_tonumber(L, 1) == 6);
REQUIRE(lua_tonumber(L, 2) == 7);
REQUIRE(lua_tonumber(L, 3) == 1);
REQUIRE(lua_tonumber(L, 4) == 2);
REQUIRE(lua_tonumber(L, 5) == 3);
REQUIRE(lua_tonumber(L, 6) == 4);
REQUIRE(lua_tonumber(L, 7) == 5);
}
SECTION("rotate all down 2") {
luaT_rotate(L, 1, -2);
REQUIRE(lua_tonumber(L, 1) == 3);
REQUIRE(lua_tonumber(L, 2) == 4);
REQUIRE(lua_tonumber(L, 3) == 5);
REQUIRE(lua_tonumber(L, 4) == 6);
REQUIRE(lua_tonumber(L, 5) == 7);
REQUIRE(lua_tonumber(L, 6) == 1);
REQUIRE(lua_tonumber(L, 7) == 2);
}
SECTION("rotate from 3rd up 1") {
luaT_rotate(L, 3, 1);
REQUIRE(lua_tonumber(L, 1) == 1);
REQUIRE(lua_tonumber(L, 2) == 2);
REQUIRE(lua_tonumber(L, 3) == 7);
REQUIRE(lua_tonumber(L, 4) == 3);
REQUIRE(lua_tonumber(L, 5) == 4);
REQUIRE(lua_tonumber(L, 6) == 5);
REQUIRE(lua_tonumber(L, 7) == 6);
}
SECTION("rotate from -3rd up 1") {
luaT_rotate(L, -3, 1);
REQUIRE(lua_tonumber(L, 1) == 1);
REQUIRE(lua_tonumber(L, 2) == 2);
REQUIRE(lua_tonumber(L, 3) == 3);
REQUIRE(lua_tonumber(L, 4) == 4);
REQUIRE(lua_tonumber(L, 5) == 7);
REQUIRE(lua_tonumber(L, 6) == 5);
REQUIRE(lua_tonumber(L, 7) == 6);
}
SECTION("rotate from -5th down 2") {
luaT_rotate(L, -5, -2);
REQUIRE(lua_tonumber(L, 1) == 1);
REQUIRE(lua_tonumber(L, 2) == 2);
REQUIRE(lua_tonumber(L, 3) == 5);
REQUIRE(lua_tonumber(L, 4) == 6);
REQUIRE(lua_tonumber(L, 5) == 7);
REQUIRE(lua_tonumber(L, 6) == 3);
REQUIRE(lua_tonumber(L, 7) == 4);
}
lua_close(L);
}

View File

@@ -209,7 +209,7 @@ Divides research input to get the amount of research points. must be > 0
--------------------- Competitor Information -----------------------
| Index in the away "computer" | Is that opponent playing? | 1 is yes, 0 no | Comment |
#computer[12].Playing 1 CORSIX
#computer[13].Playing 1 ROUJIN
#computer[14].Playing 1 EDVIN
| Index in the away "computer" | Is that opponent playing? 1 is yes, 0 no | Name |
#computer[12].Playing.Name 1 CORSIX
#computer[13].Playing.Name 1 ROUJIN
#computer[14].Playing.Name 1 EDVIN

View File

@@ -206,10 +206,10 @@ Divides research input to get the amount of research points. must be > 0
--------------------- Competitor Information -----------------------
| Index in the away "computer" | Is that opponent playing? | 1 is yes, 0 no | Comment |
#computer[12].Playing 1 CORSIX
#computer[13].Playing 1 ROUJIN
#computer[14].Playing 1 EDVIN
| Index in the away "computer" | Is that opponent playing? 1 is yes, 0 no | Name |
#computer[12].Playing.Name 1 CORSIX
#computer[13].Playing.Name 1 ROUJIN
#computer[14].Playing.Name 1 EDVIN
----------------------- Emergency Control --------------------------

View File

@@ -108,6 +108,7 @@ When a drug is researched, what effectiveness does it have
--------------------- Competitor Information -----------------------
#computer[12].Playing 1 CORSIX
#computer[13].Playing 1 ROUJIN
#computer[14].Playing 1 EDVIN
| Index in the away "computer" | Is that opponent playing? 1 is yes, 0 no | Name |
#computer[12].Playing.Name 1 CORSIX
#computer[13].Playing.Name 1 ROUJIN
#computer[14].Playing.Name 1 EDVIN

View File

@@ -55,7 +55,7 @@ InterestRate is defined as centiprocent to allow for two decimals precision, i.e
-------------------- Disease Configuration -------------------------
When a drug is researched, what effectiveness does it have
#gbv.StartRating 100
#gbv.StartRating 100
The following table contains all diagnoses and treatments that shows up in the drug casebook
in the game. Known specifies whether it should show up from the beginning of the level and
@@ -230,10 +230,10 @@ But we don't want any such conditions on the example level!
--------------------- Competitor Information -----------------------
| Index in the away "computer" | Is that opponent playing? | 1 is yes, 0 no | Comment |
#computer[12].Playing 1 CORSIX
#computer[13].Playing 1 ROUJIN
#computer[14].Playing 1 EDVIN
| Index in the away "computer" | Is that opponent playing? 1 is yes, 0 no | Name |
#computer[12].Playing 1.Name CORSIX
#computer[13].Playing 1.Name ROUJIN
#computer[14].Playing 1.Name EDVIN
----------------------- Emergency Control --------------------------

View File

@@ -280,7 +280,7 @@ But we don't want any such conditions on the example level!
--------------------- Competitor Information -----------------------
| Index in the away "computer" | Is that opponent playing? | 1 is yes, 0 no | Comment |
#computer[12].Playing 1 CORSIX
#computer[13].Playing 1 ROUJIN
#computer[14].Playing 1 EDVIN
| Index in the away "computer" | Is that opponent playing? 1 is yes, 0 no | Name |
#computer[12].Playing.Name 1 CORSIX
#computer[13].Playing.Name 1 ROUJIN
#computer[14].Playing.Name 1 EDVIN

View File

@@ -372,7 +372,7 @@ But we don't want any such conditions on the example level!
--------------------- Competitor Information -----------------------
| Index in the away "computer" | Is that opponent playing? | 1 is yes, 0 no | Comment |
#computer[12].Playing 1 Corsix
#computer[13].Playing 1 Roujin
#computer[14].Playing 1 Edvin
| Index in the away "computer" | Is that opponent playing? 1 is yes, 0 no | Name |
#computer[12].Playing.Name 1 Corsix
#computer[13].Playing.Name 1 Roujin
#computer[14].Playing.Name 1 Edvin

View File

@@ -18,6 +18,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. --]]
corsixth.require("date")
--! Pr
local AnnouncementPriority = {
Critical = 1,
@@ -30,12 +32,13 @@ strict_declare_global "AnnouncementPriority"
_G["AnnouncementPriority"] = AnnouncementPriority
local default_announcement_priority = AnnouncementPriority.Normal
local hoursPerDay = Date.hoursPerDay()
local default_announcement_decay_hours = {
[AnnouncementPriority.Critical] = -1, -- never decay
[AnnouncementPriority.High] = 365*24,
[AnnouncementPriority.Normal] = 31*24,
[AnnouncementPriority.Low] = 7*24
[AnnouncementPriority.High] = 31 * hoursPerDay,
[AnnouncementPriority.Normal] = 7 * hoursPerDay,
[AnnouncementPriority.Low] = 3 * hoursPerDay
}
--! An announcement queue based on priority
@@ -84,6 +87,22 @@ function AnnouncementQueue:isEmpty()
return self.count == 0
end
--! Checks for duplicates in the announcement queue and refreshes the announcement's created_date
--!param sound the announcement to check
--!param date the date to use, usually the current date
function AnnouncementQueue:checkForDuplicates(sound, date)
for _, entries in ipairs(self.priorities) do
for _, entry in ipairs(entries) do
if entry.name == sound then
entry.created_date = date
return true
end
end
end
return false
end
--! An announcement.
class "AnnouncementEntry"
@@ -94,8 +113,8 @@ local AnnouncementEntry = _G["AnnouncementEntry"]
function AnnouncementEntry:AnnouncementEntry()
self.name = nil -- filename to play
self.priority = default_announcement_priority
self.created_tick = nil -- when it has been created
self.decay_ticks = nil -- how long until the announcement isn't relevant anymore
self.created_date = nil -- when it has been created
self.decay_hours = nil -- how long until the announcement isn't relevant anymore
self.played_callback = nil -- call me whenever the sound was played, ...
self.played_callback_delay = nil -- but not until delay has passed
end
@@ -137,8 +156,15 @@ function Announcer:playAnnouncement(name, priority, decay_hours, played_callback
-- It doesn't make sense to play the sacked announcement when the employee
-- already has had several other jobs and has died already [joke].
-- Check for duplicate announcements, if there is we refresh the existing one
local created_date = self.app.world:date()
local duplicate_announcement = self.entries:checkForDuplicates(name, created_date)
if duplicate_announcement then
return
end
local new_priority = priority or default_announcement_priority
local created_date = self.app.world.game_date:clone()
local new_decay_hours = decay_hours or default_announcement_decay_hours[new_priority]
local entry = AnnouncementEntry()
@@ -149,13 +175,17 @@ function Announcer:playAnnouncement(name, priority, decay_hours, played_callback
entry.played_callback = played_callback
entry.played_callback_delay = played_callback_delay
self.entries:push(new_priority, entry)
if self.app.world:getLocalPlayerHospital():hasStaffedDesk() or priority == AnnouncementPriority.Critical then
self.entries:push(new_priority, entry)
end
end
--! The announcer's (game) tick handler.
-- Plays the actual sound of the announcements, if available.
-- Also queues random announcements if no announcements have been played for a while.
function Announcer:onTick()
local staffedDesk = self.app.world:getLocalPlayerHospital():hasStaffedDesk()
local criticalAnnounces = #self.entries.priorities[AnnouncementPriority.Critical]
if not self.app.world:isCurrentSpeed("Pause") then
local ticks_since_last_announcement = self.ticks_since_last_announcement
if ticks_since_last_announcement >= self.random_announcement_ticks_target then
@@ -164,19 +194,28 @@ function Announcer:onTick()
else
self.ticks_since_last_announcement = ticks_since_last_announcement + 1
end
end
-- Delay until someone is available at the desk
if self.app.world:getLocalPlayerHospital():hasStaffedDesk() then
while not self.playing and not self.entries:isEmpty() do
local entry = self.entries:pop()
local game_date = self.app.world.game_date
-- Wait for an occupied desk or announcement is critical
if staffedDesk or criticalAnnounces > 0 then
while not self.playing and not self.entries:isEmpty() do
local entry = self.entries:pop()
local game_date = self.app.world:date()
if entry.decay_hours == -1 or game_date <= entry.created_date:plusHours(entry.decay_hours) then
if self.app.config.play_announcements then
self:_play(entry)
if entry.decay_hours == -1 or game_date <= entry.created_date:plusHours(entry.decay_hours) then
if self.app.config.play_announcements then
self:_play(entry)
end
-- Drain the queue otherwise
end
-- Drain the queue otherwise
end
end
-- The game is paused
else
-- Only play critical announcements when paused
while not self.playing and not self.entries:isEmpty() and criticalAnnounces > 0 do
local entry = self.entries:pop()
if self.app.config.play_announcements then
self:_play(entry)
end
end
end

View File

@@ -32,4 +32,4 @@ Note: This file compiles as both Lua and C++. */
#endif /*]]--*/
return 2678;
return 2679;

View File

@@ -28,7 +28,7 @@ local runDebugger = corsixth.require("run_debugger")
-- Increment each time a savegame break would occur
-- and add compatibility code in afterLoad functions
local SAVEGAME_VERSION = 138
local SAVEGAME_VERSION = 155
class "App"
@@ -121,10 +121,13 @@ function App:init()
self:initScreenshotsDir()
-- Create the window
if not SDL.init("audio", "video", "timer") then
if not SDL.init("video", "timer") then
return false, "Cannot initialise SDL"
end
local compile_opts = TH.GetCompileOptions()
if compile_opts.audio then
SDL.init("audio")
end
local api_version = corsixth.require("api_version")
if api_version ~= compile_opts.api_version then
api_version = api_version or 0
@@ -137,6 +140,11 @@ function App:init()
end
end
-- Report operating system
if compile_opts.os then
self.os = compile_opts.os
end
local modes = {}
if compile_opts.renderer == "OpenGL" then
modes[#modes + 1] = "opengl"
@@ -316,6 +324,18 @@ function App:init()
"Please make sure you have specified a font file in the config file."}))
end
-- If the player wants to continue then load the youngest file in the Autosaves folder
-- If they give a month number then load that month's autosave
if self.command_line.continue then
local num = self.command_line.continue
local file = "Autosaves" .. pathsep .. "Autosave" .. num .. ".sav"
if num >= "1" and num <= "12" and lfs.attributes(self.savegame_dir .. file, "size") then
self.command_line.load = file
else
self.command_line.load = "Autosaves" .. pathsep ..
FileTreeNode(self.savegame_dir .. "Autosaves"):getMostRecentlyModifiedChildFile(".sav").label
end
end
-- If a savegame was specified, load it
if self.command_line.load then
local status, err = pcall(self.load, self, self.savegame_dir .. self.command_line.load)
@@ -606,21 +626,7 @@ function App:loadLevel(level, difficulty, level_name, level_file, level_intro, m
return
end
-- If going from another level, save progress.
local carry_to_next_level
if self.world and self.world.campaign_info then
carry_to_next_level = {
world = {
room_built = self.world.room_built,
campaign_info = self.world.campaign_info,
},
hospital = {
player_salary = self.ui.hospital.player_salary,
message_popup = self.ui.hospital.message_popup,
hospital_littered = self.ui.hospital.hospital_littered,
has_seen_pay_rise = self.ui.hospital.has_seen_pay_rise,
},
}
end
local campaign_data = self.world and self.world:getCampaignData()
-- Make sure there is no blue filter active.
self.video:setBlueFilterActive(false)
@@ -646,13 +652,9 @@ function App:loadLevel(level, difficulty, level_name, level_file, level_intro, m
self.ui = GameUI(self, self.world:getLocalPlayerHospital(), map_editor)
self.world:setUI(self.ui) -- Function call allows world to set up its keyHandlers
if tonumber(level) then
self.moviePlayer:playAdvanceMovie(level)
end
-- Now restore progress from previous levels.
if carry_to_next_level then
self.world:initFromPreviousLevel(carry_to_next_level)
if campaign_data then
self.world:setCampaignData(campaign_data)
end
end
@@ -730,7 +732,7 @@ function App:dumpStrings()
self:checkMissingStringsInLanguage(dir, self.config.language)
-- Uncomment these lines to get diffs for all languages in the game
-- for _, lang in ipairs(self.strings.languages_english) do
-- for _, lang in pairs(self.strings.languages_english) do
-- self:checkMissingStringsInLanguage(dir, lang)
-- end
print("")
@@ -746,7 +748,6 @@ end
--!param dir The directory where the file to write to should be.
--!param language The language to check against.
function App:checkMissingStringsInLanguage(dir, language)
-- Accessors to reach through the userdata proxies on strings
local LUT = debug.getregistry().StringProxyValues
local function val(o)
@@ -788,7 +789,7 @@ function App:checkMissingStringsInLanguage(dir, language)
-- if possible, use the English name of the language for the file name.
local language_english = language
for _, lang_eng in ipairs(self.strings.languages_english) do
for _, lang_eng in pairs(self.strings.languages_english) do
if ltc[language] == ltc[lang_eng:lower()] then
language_english = lang_eng
break
@@ -853,7 +854,8 @@ end
function App:saveConfig()
-- Load lines from config file
local fi = io.open(self.command_line["config-file"] or "config.txt", "r")
local config_file = self.command_line["config-file"] or "config.txt"
local fi = io.open(config_file, "r")
local lines = {}
local handled_ids = {}
if fi then
@@ -904,13 +906,29 @@ function App:saveConfig()
lines[#lines] = nil
end
fi = io.open(self.command_line["config-file"] or "config.txt", "w")
fi = self:writeToFileOrTmp(config_file)
for _, line in ipairs(lines) do
fi:write(line .. "\n")
end
fi:close()
end
--! Tries to open the given file or a file in OS's temp dir.
-- Returns the file handler
--!param file The full path of the intended file
--!param mode The mode in which the file is opened, defaults to write
function App:writeToFileOrTmp(file, mode)
local f, err = io.open(file, mode or "w")
if err then
local tmp_file = os.tmpname()
f = io.open(tmp_file, mode or "w")
self.ui:addWindow(UIInformation(self.ui,
{_S.errors.save_to_tmp:format(file, tmp_file, err)}))
end
assert(f, "Error: cannot write to filesystem")
return f
end
function App:fixHotkeys()
-- Fill in default values for things which don't exist
local hotkeys_defaults = select(4, corsixth.require("config_finder"))
@@ -934,7 +952,8 @@ end
function App:saveHotkeys()
-- Load lines from config file
local fi = io.open(self.command_line["hotkeys-file"] or "hotkeys.txt", "r")
local hotkeys_filename = self.command_line["hotkeys-file"] or "hotkeys.txt"
local fi = io.open(hotkeys_filename, "r")
local lines = {}
local handled_ids = {}
@@ -988,7 +1007,7 @@ function App:saveHotkeys()
lines[#lines] = nil
end
fi = io.open(self.command_line["hotkeys-file"] or "hotkeys.txt", "w")
fi = self:writeToFileOrTmp(hotkeys_filename)
for _, line in ipairs(lines) do
fi:write(line .. "\n")
end
@@ -1035,6 +1054,7 @@ function App:run()
local e, where = SDL.mainloop(co)
debug.sethook(co, nil)
self.running = false
self.video:setCaptureMouse(false) -- Free the mouse, so the user can eg close the window.
if e ~= nil then
if where then
-- Errors from an asynchronous callback done on the dispatcher coroutine
@@ -1203,6 +1223,21 @@ function App:onMultiGesture(...)
return self.ui:onMultiGesture(...)
end
function App:isThemeHospitalPath(path)
local ngot = 0
for obj, _ in lfs.dir(path) do
for _, thing in ipairs({"data", "levels", "qdata"}) do
if obj:lower() == thing and
lfs.attributes(path .. pathsep .. obj, "mode") == "directory" then
ngot = ngot + 1
end
end
end
if ngot == 3 then
return true
end
end
function App:checkInstallFolder()
self.fs = FileSystem()
local status, _
@@ -1213,10 +1248,41 @@ function App:checkInstallFolder()
" a valid copy of the data files from the original game," ..
" as said files are required for graphics and sounds."
if not status then
-- If the given directory didn't exist, then likely the config file hasn't
-- been changed at all from the default, so we continue to initialise the
-- app, and give the user a dialog asking for the correct directory.
return false
-- Table of predictable places. First three are platform independent,
-- then macOS app and its parent folder, GOG bundle,
-- then linux Filesystem Hierarchy Standard, then Windows Program Files
-- mac_app_dir is the macOS app base directory named CorsixTH.app
local mac_app_dir = debug.getinfo(1).short_src:match("(.*)/Contents/.")
local user_dir = os.getenv("HOME") or os.getenv("HOMEPATH") or ""
local possible_locations = { user_dir, user_dir .. pathsep .. "Documents",
select(1, corsixth.require("config_finder")):match("(.*[/\\])"):sub(1, -2),
mac_app_dir, mac_app_dir and mac_app_dir:match("(.*)/.*%.app"),
"/Applications/Theme Hospital.app/Contents/Resources/game/Theme Hospital.app/" ..
"Contents/Resources/Theme Hospital.boxer/C.harddisk",
"/usr/share/games/corsix-th", "/usr/local/share/games/corsix-th",
os.getenv("ProgramFiles"), os.getenv("ProgramFiles(x86)") }
local possible_folders = { "ThemeHospital", "Theme Hospital", "HOSP", "TH97",
[[GOG.com\Theme Hospital]], [[Origin Games\Theme Hospital\data\Game]] }
for _, dir in ipairs(possible_locations) do
if status then break end
for _, folder in ipairs(possible_folders) do
local path = dir .. pathsep .. folder
if lfs.attributes(path, "mode") == "directory" and self:isThemeHospitalPath(path) then
print("Game data found at: " .. path)
print("This will be written to the config file")
self.config.theme_hospital_install = path
status, _ = self.fs:setRoot(path)
break
end
end
end
if not status then
-- If the given directory didn't exist, then likely the config file hasn't
-- been changed at all from the default, and we looked unsuccessfully in
-- some likely folders for the game data, so we continue to initialise the
-- app, and give the user a dialog asking for the correct directory.
return false
end
end
-- Check that a few core files are present
@@ -1403,7 +1469,7 @@ function App:getVersion(version)
if ver > 138 then
return "Trunk"
elseif ver > 134 then
return "v0.64-rc1"
return "v0.64"
elseif ver > 127 then
return "v0.63"
elseif ver > 122 then
@@ -1460,6 +1526,19 @@ function App:quickLoad()
end
end
--! Function to check the loaded game is compatible with the program
--!param save_version (num)
--!return true if compatible, otherwise false
function App:checkCompatibility(save_version)
local app_version = self.savegame_version
if app_version >= save_version or self.config.debug then
return true
else
UILoadGame:loadError(_S.errors.compatibility_error)
return false
end
end
--! Restarts the current level (offers confirmation window first)
function App:restart()
assert(self.map, "Trying to restart while no map is loaded.")
@@ -1519,20 +1598,27 @@ function App:afterLoad()
self.world.original_savegame_version = old
end
local first = self.world.original_savegame_version
-- Generate the human-readable version number (old [loaded save], new [program], first [original])
local first_version = first .. " (" .. self:getVersion(first) .. ")"
local old_version = old .. " (" .. self:getVersion(old) .. ")"
local new_version = new .. " (" .. self:getVersion() .. ")"
if new == old then
self.world:gameLog("Savegame version is " .. new .. " (" .. self:getVersion() ..
"), originally it was " .. first .. " (" .. self:getVersion(first) .. ")")
local msg_same = "Savegame version is %s, originally it was %s."
self.world:gameLog(msg_same:format(new_version, first_version))
self.world:playLoadedEntitySounds()
elseif new > old then
self.world:gameLog("Savegame version changed from " .. old .. " (" .. self:getVersion(old) ..
") to " .. new .. " (" .. self:getVersion() ..
"). The save was created using " .. first ..
" (" .. self:getVersion(first) .. ")")
else
-- TODO: This should maybe be forbidden completely.
self.world:gameLog("Warning: loaded savegame version " .. old .. " (" .. self:getVersion(old) ..
")" .. " in older version " .. new .. " (" .. self:getVersion() .. ").")
local msg_older = "Savegame changed from %s to %s. The save was created using %s."
self.world:gameLog(msg_older:format(old_version, new_version, first_version))
else -- Save is newer than the game and can only proceed in debug mode
local get_old_release_version = self.world.release_version or "Trunk" -- For compatibility
old_version = old .. " (" .. get_old_release_version .. ")"
local msg_newer = "Warning: loaded savegame version %s in older version %s."
self.world:gameLog(msg_newer:format(old_version, new_version))
self.ui:addWindow(UIInformation(self.ui, {_S.warnings.newersave}))
end
self.world.release_version = self:getVersion()
self.world.savegame_version = new
if old < 87 then
@@ -1561,11 +1647,11 @@ function App:checkForUpdates()
-- Default language to use for the changelog if no localised version is available
local default_language = "en"
local update_url = 'www.corsixth.com/check-for-updates'
local update_url = 'https://corsixth.github.io/CorsixTH/check-for-updates'
local current_version = self:getVersion()
-- Only URLs that match this list of trusted domains will be accepted.
local trusted_domains = { 'corsixth.com', 'code.google.com' }
local trusted_domains = { 'corsixth.com', 'github.com', 'corsixth.github.io' }
-- Only check for updates against released versions
if current_version == "Trunk" then
@@ -1573,26 +1659,15 @@ function App:checkForUpdates()
return
end
local success, _ = pcall(require, "socket")
if not success then
-- LuaSocket is not available, just return
print("Cannot check for updates since LuaSocket is not available.")
local luasocket, _ = pcall(require, "socket")
local luasec, _ = pcall(require, "ssl.https")
if not (luasocket and luasec) then
print("Cannot check for updates since LuaSocket and/or LuaSec are not available.")
return
else
self.lua_socket_available = true
end
local http = require("socket.http")
local url = require("socket.url")
-- Safely attempt to use luasec
local hassec, _ = pcall(require, "ssl.https")
if hassec then
update_url = "https://" .. update_url
else
update_url = "http://" .. update_url
end
print("Checking for CorsixTH updates...")
local update_body, status, _ = http.request(update_url)
@@ -1653,6 +1728,10 @@ function App:finishVideoUpdate()
self.video:startFrame()
end
function App:isAudioEnabled()
return TH.GetCompileOptions().audio
end
-- Do not remove, for savegame compatibility < r1891
local app_confirm_quit_stub = --[[persistable:app_confirm_quit]] function()
end

View File

@@ -25,9 +25,13 @@ local configuration = {
-----------------------------------------------------------
-- New configuration values added in CorsixTH --
-----------------------------------------------------------
-- Interest rate and overdraft interest rate differential values will
-- be divided by 10,000
town = {
InterestRate = 0.01,
InterestRate = 100,
StartCash = 40000,
StartRep = 500,
OverdraftDiff = 200,
},
-- New value, but should only be defined if starting staff is included.
@@ -133,19 +137,19 @@ local configuration = {
},
towns = {
{StartCash = 40000, InterestRate = 100}, -- Level 1
{StartCash = 40000, InterestRate = 200}, -- Level 2
{StartCash = 50000, InterestRate = 300}, -- Level 3
{StartCash = 50000, InterestRate = 400}, -- Level 4
{StartCash = 50000, InterestRate = 500}, -- Level 5
{StartCash = 50000, InterestRate = 600}, -- Level 6
{StartCash = 50000, InterestRate = 700}, -- Level 7
{StartCash = 60000, InterestRate = 700}, -- Level 8
{StartCash = 60000, InterestRate = 800}, -- Level 9
{StartCash = 60000, InterestRate = 800}, -- Level 10
{StartCash = 70000, InterestRate = 900}, -- Level 11
{StartCash = 70000, InterestRate = 900}, -- Level 12
{StartCash = 70000, InterestRate = 900}, -- Level 12
{StartCash = 40000, InterestRate = 100, StartRep = 500, OverdraftDiff = 200}, -- Level 1
{StartCash = 40000, InterestRate = 200, StartRep = 500, OverdraftDiff = 200}, -- Level 2
{StartCash = 50000, InterestRate = 300, StartRep = 500, OverdraftDiff = 200}, -- Level 3
{StartCash = 50000, InterestRate = 400, StartRep = 500, OverdraftDiff = 200}, -- Level 4
{StartCash = 50000, InterestRate = 500, StartRep = 500, OverdraftDiff = 200}, -- Level 5
{StartCash = 50000, InterestRate = 600, StartRep = 500, OverdraftDiff = 200}, -- Level 6
{StartCash = 50000, InterestRate = 700, StartRep = 500, OverdraftDiff = 200}, -- Level 7
{StartCash = 60000, InterestRate = 700, StartRep = 500, OverdraftDiff = 200}, -- Level 8
{StartCash = 60000, InterestRate = 800, StartRep = 500, OverdraftDiff = 200}, -- Level 9
{StartCash = 60000, InterestRate = 800, StartRep = 500, OverdraftDiff = 200}, -- Level 10
{StartCash = 70000, InterestRate = 900, StartRep = 500, OverdraftDiff = 200}, -- Level 11
{StartCash = 70000, InterestRate = 900, StartRep = 500, OverdraftDiff = 200}, -- Level 12
{StartCash = 70000, InterestRate = 900, StartRep = 500, OverdraftDiff = 200}, -- Level 12
},
popn = {
[0] = {Month = 0, Change = 4}, -- Standard: 4 patients the first month.
@@ -372,21 +376,21 @@ local configuration = {
[0] = {StartMonth = 0, EndMonth = 0, Min = 0, Max = 0, Illness = 0, PercWin = 0, Bonus = 0},
},
computer = {
[0] = {Playing = 0}, -- ORAC
{Playing = 0}, -- COLOSSUS
{Playing = 0}, -- HAL
{Playing = 0}, -- MULTIVAC
{Playing = 0}, -- HOLLY
{Playing = 0}, -- DEEP THOUGHT
{Playing = 0}, -- ZEN
{Playing = 0}, -- SKYNET
{Playing = 0}, -- MARVIN
{Playing = 0}, -- CEREBRO
{Playing = 0}, -- MOTHER
{Playing = 0}, -- JAYNE
{Playing = 0}, -- CORSIX
{Playing = 0}, -- ROUJIN
{Playing = 0}, -- EDVIN
[0] = {Playing = 0, Name = "ORAC"},
{Playing = 0, Name = "COLOSSUS"},
{Playing = 0, Name = "HAL"},
{Playing = 0, Name = "MULTIVAC"},
{Playing = 0, Name = "HOLLY"},
{Playing = 0, Name = "DEEP THOUGHT"},
{Playing = 0, Name = "ZEN"},
{Playing = 0, Name = "SKYNET"},
{Playing = 0, Name = "MARVIN"},
{Playing = 0, Name = "CEREBRO"},
{Playing = 0, Name = "MOTHER"},
{Playing = 0, Name = "JAYNE"},
{Playing = 0, Name = "CORSIX"},
{Playing = 0, Name = "ROUJIN"},
{Playing = 0, Name = "EDVIN"},
},
awards_trophies = {
@@ -427,9 +431,11 @@ local configuration = {
PlantBonus = 5,
-- Bonus - MIN 0 MAX 255 (REP BONUS)
TrophyStaffHappinessBonus = 5,
-- Bonus to money for curing every patient, no deaths or send-homes (MONEY BONUS)
TrophyAllCuredBonus = 20000,
-- Bonus to money for NO DEATHS in the year (MONEY BONUS)
TrophyDeathBonus = 10000,
-- Bonus to money for approximately 100% Cure Rate in the year (MONEY BONUS)
-- Bonus to money for over 90% Cure Rate in the year (MONEY BONUS)
TrophyCuresBonus = 6000,
-- Bonus to reputation for pleasing VIPs in the year (REPUTATION BONUS)
TrophyMayorBonus = 5,
@@ -495,6 +501,8 @@ local configuration = {
-- MIN -32000 MAX +32000 - MONEY
CuresPenalty = -3000,
-- MIN -32000 MAX +32000 - MONEY
AllCuresBonus = 5000,
-- MIN -32000 MAX +32000 - MONEY
DeathsBonus = 3000,
-- MIN -32000 MAX +32000 - MONEY
DeathsPenalty = -5000,

View File

@@ -115,18 +115,12 @@ function CallsDispatcher:callForRepair(object, urgent, manual, lock_room)
object:setRepairingMode(lock_room and true or false)
local message
local ui = object.world.ui
if not object.world:getLocalPlayerHospital():hasStaffOfCategory("Handyman") then
if object.world:getLocalPlayerHospital():countStaffOfCategory("Handyman", 1) == 0 then
-- Advise about hiring Handyman
message = _A.warnings.machinery_damaged2
end
if not manual and urgent then
local room = object:getRoom()
local sound = room.room_info.handyman_call_sound
if sound then
ui:playAnnouncement(sound, AnnouncementPriority.High)
ui:playSound("machwarn.wav")
end
message = _A.warnings.machines_falling_apart
end
if message then
@@ -523,7 +517,7 @@ function CallsDispatcher.getPriorityForRoom(room, attribute, staff)
end
end
-- Prefer the tirer staff (such that less chance to have "resting sychronization issue")
-- Prefer the tirer staff (such that less chance to have "resting synchronization issue")
score = score - staff.attributes["fatigue"] * 40 -- 40 is just a weighting scale
-- TODO: Assign doctor with higher ability

171
CorsixTH/Lua/cheats.lua Normal file
View File

@@ -0,0 +1,171 @@
--[[ Copyright (c) 2010 Manuel "Roujin" Wolf
Copyright (c) 2020 lewri
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. --]]
corsixth.require("announcer")
local AnnouncementPriority = _G["AnnouncementPriority"]
--! A holder for all cheats in the game
class "Cheats"
---@type Cheats
local Cheats = _G["Cheats"]
-- Cheats System
function Cheats:Cheats(hospital)
self.hospital = hospital
-- Cheats to appear specifically in the cheats window
-- New cheats require a persistable and a wrapped function in func
self.cheat_list = {
{name = "money", func = self.cheatMoney},
{name = "all_research", func = self.cheatResearch},
{name = "emergency", func = self.cheatEmergency},
{name = "epidemic", func = self.cheatEpidemic},
{name = "toggle_infected", func = self.cheatToggleInfected},
{name = "vip", func = self.cheatVip},
{name = "earthquake", func = self.cheatEarthquake},
{name = "create_patient", func = self.cheatPatient},
{name = "end_month", func = self.cheatMonth},
{name = "end_year", func = self.cheatYear},
{name = "lose_level", func = self.cheatLose},
{name = "win_level", func = self.cheatWin},
{name = "increase_prices", func = self.cheatIncreasePrices},
{name = "decrease_prices", func = self.cheatDecreasePrices},
}
end
--! Performs a cheat from the cheat_list
--!param num (integer) The cheat from the cheat_list called
--!return true if cheat was successful, false otherwise
function Cheats:performCheat(num)
local cheat_success = self.cheat_list[num].func(self) ~= false
return cheat_success and self.cheat_list[num].name ~= "lose_level"
end
--! Updates the cheated status of the player, with a matching announcement
function Cheats:announceCheat()
local announcements = self.hospital.world.cheat_announcements
if announcements then
self.hospital.world.ui:playAnnouncement(announcements[math.random(1, #announcements)], AnnouncementPriority.Critical)
end
self.hospital.cheated = true
end
function Cheats:cheatMoney()
self.hospital:receiveMoney(10000, _S.transactions.cheat)
end
function Cheats:cheatResearch()
local hosp = self.hospital
for _, cat in ipairs({"diagnosis", "cure"}) do
while hosp.research.research_policy[cat].current do
hosp.research:discoverObject(hosp.research.research_policy[cat].current)
end
end
end
function Cheats:cheatEmergency()
local err = self.hospital:createEmergency()
local ui = self.hospital.world.ui
if err == "undiscovered_disease" then
ui:addWindow(UIInformation(ui, {_S.misc.cant_treat_emergency}))
elseif err == "no_heliport" then
ui:addWindow(UIInformation(ui, {_S.misc.no_heliport}))
-- else 'err == nil', meaning success. The case doesn't need special handling
end
end
--[[ Creates a new contagious patient in the hospital - potentially an epidemic]]
function Cheats:cheatEpidemic()
self.hospital:spawnContagiousPatient()
end
--[[ Before an epidemic has been revealed toggle the infected icons
to easily distinguish the infected patients -- will toggle icons
for ALL future epidemics you cannot distinguish between epidemics
by disease ]]
function Cheats:cheatToggleInfected()
local hosp = self.hospital
if hosp.future_epidemics_pool and #hosp.future_epidemics_pool > 0 then
for _, future_epidemic in ipairs(hosp.future_epidemics_pool) do
local show_mood = future_epidemic.cheat_always_show_mood
future_epidemic.cheat_always_show_mood = not show_mood
local mood_action = show_mood and "deactivate" or "activate"
for _, patient in ipairs(future_epidemic.infected_patients) do
patient:setMood("epidemy4",mood_action)
end
end
else
self.hospital.world:gameLog("Unable to toggle icons - no epidemics in progress that are not revealed")
end
end
function Cheats:cheatVip()
self.hospital:createVip()
end
function Cheats:cheatEarthquake()
return self.hospital.world:createEarthquake()
end
function Cheats:cheatPatient()
self.hospital.world:spawnPatient()
end
function Cheats:cheatMonth()
self.hospital.world:setEndMonth()
end
function Cheats:cheatYear()
self.hospital.world:setEndYear()
end
function Cheats:cheatLose()
self.hospital.world:loseGame(1) -- TODO adjust for multiplayer
end
function Cheats:cheatWin()
self.hospital.world:winGame(1) -- TODO adjust for multiplayer
end
function Cheats:cheatIncreasePrices()
local hosp = self.hospital
for _, casebook in pairs(hosp.disease_casebook) do
local new_price = casebook.price + 0.5
if new_price > 2 then
casebook.price = 2
else
casebook.price = new_price
end
end
end
function Cheats:cheatDecreasePrices()
local hosp = self.hospital
for _, casebook in pairs(hosp.disease_casebook) do
local new_price = casebook.price - 0.5
if new_price < 0.5 then
casebook.price = 0.5
else
casebook.price = new_price
end
end
end

View File

@@ -120,6 +120,7 @@ local config_defaults = {
alien_dna_can_knock_on_doors = false,
disable_fractured_bones_females = true,
enable_avg_contents = false,
remove_destroyed_rooms = false,
audio_frequency = 22050,
audio_channels = 2,
audio_buffer_size = 2048,
@@ -138,6 +139,7 @@ local config_defaults = {
new_graphics_folder = nil,
use_new_graphics = false,
check_for_updates = true,
room_information_dialogs = true
}
fi = io.open(config_filename, "r")
@@ -172,9 +174,7 @@ else
end
if needs_rewrite then
fi = io.open(config_filename, "w")
if fi then
fi:write([=[
local string_01 = [=[
----------------------------------------- CorsixTH configuration file -------------------------------------------
-- Lines starting with two dashes (like this one) are ignored.
-- Text settings should have their values between double square braces, e.g.
@@ -360,6 +360,13 @@ if needs_rewrite then
-- If you would like the game to remember what you usually add, then change this option to true.
-- ]=] .. '\n' ..
'enable_avg_contents = ' .. tostring(config_values.enable_avg_contents) .. '\n' .. [=[
-------------------------------------------------------------------------------------------------------------------------
-- By default destroyed rooms can't be removed.
-- If you would like the game to give you the option of removing a destroyed room change this option to true.
-- ]=] .. '\n' ..
'remove_destroyed_rooms = ' .. tostring(config_values.remove_destroyed_rooms) .. '\n' .. [=[]=]
local string_02 = [=[
----------------------------------------------- FOLDER SETTINGS ----------------------------------------------
-- These settings can also be changed from the Folders Menu
@@ -482,6 +489,19 @@ audio_music = nil -- [[X:\ThemeHospital\Music]]
'scroll_speed = ' .. tostring(config_values.scroll_speed) .. '\n' ..
'shift_scroll_speed = ' .. tostring(config_values.shift_scroll_speed) .. '\n' .. [=[
-------------------------------------------------------------------------------------------------------------------------
-- Room information dialogs: Information about new rooms, important for
-- additional rooms in later levels. Affects campaign only.
-- ]=] .. '\n' ..
'room_information_dialogs = ' .. tostring(config_values.room_information_dialogs) .. '\n' .. [=[
-------------------------------------------------------------------------------------------------------------------------
-- If true, parts of the hospital can be made inaccessible by blocking the path
-- with rooms or objects. If false, all parts of the hospital must be kept
-- accessible, the game will disallow any attempt to blocking the path.
-- ]=] .. '\n' ..
'allow_blocking_off_areas = ' .. tostring(config_values.allow_blocking_off_areas) .. '\n' .. [=[
-------------------------------------------------------------------------------------------------------------------------
@@ -497,9 +517,10 @@ audio_music = nil -- [[X:\ThemeHospital\Music]]
-- you play the game.
-------------------------------------------------------------------------------------------------------------------------
]=])
fi:close()
end
]=]
fi = TheApp:writeToFileOrTmp(config_filename)
fi:write(string_01 .. string_02)
fi:close()
end
-- Hotkey filename.
@@ -523,8 +544,8 @@ local hotkeys_defaults = {
global_runDebugScript = {"shift", "d"},
global_screenshot = {"ctrl", "s"},
global_stop_movie = "escape",
global_window_close = "escape",
global_stop_movie_alt = "q",
global_window_close = "escape",
global_window_close_alt = "q",
ingame_showmenubar = "escape",
ingame_showCheatWindow = "f11",
@@ -797,11 +818,9 @@ local string_03 = [=[
-- ]=] .. '\n' ..
'ingame_patient_gohome = ' .. hotkeys_values.ingame_patient_gohome .. '\n' .. [=[
]=]
fi = io.open(hotkeys_filename, "w")
if fi then
fi:write(string_01 .. string_02 .. string_03)
fi = TheApp:writeToFileOrTmp(hotkeys_filename)
fi:write(string_01 .. string_02 .. string_03)
fi:close()
end
end
for k, str_val in pairs(hotkeys_values) do

View File

@@ -261,16 +261,17 @@ function Date_mt.__eq(one, other)
end
function Date_mt.__lt(one, other)
if one._year == other._year then
if one._month == other._month then
if one._day == other._day then
return one._hour < other._hour
end
return one._day < other._day
end
return one._month < other._month
end
return one._year < other._year
if one._year ~= other._year then return one._year < other._year end
if one._month ~= other._month then return one._month < other._month end
if one._day ~= other._day then return one._day < other._day end
return one._hour < other._hour
end
function Date_mt.__le(one, other)
if one._year ~= other._year then return one._year < other._year end
if one._month ~= other._month then return one._month < other._month end
if one._day ~= other._day then return one._day < other._day end
return one._hour <= other._hour
end
-- PRIVATE

View File

@@ -142,7 +142,9 @@ function UIBottomPanel:registerKeyHandlers()
end
function UIBottomPanel:openJukebox()
self.ui:addWindow(UIJukebox(self.ui.app))
if self.ui.app.config.audio and self.ui.app:isAudioEnabled() then
self.ui:addWindow(UIJukebox(self.ui.app))
end
end
function UIBottomPanel:openSave()
@@ -484,6 +486,7 @@ function UIBottomPanel:createMessageWindow(index)
if not message_info then
return
end
-- Create the message window, note this does not show it to the player on creation.
local alert_window = UIMessage(self.ui, 175, 1 + #message_windows * 30,
onClose, message_info.type, message_info.message, message_info.owner, message_info.timeout, message_info.default_choice, message_info.callback)
message_windows[#message_windows + 1] = alert_window
@@ -863,9 +866,10 @@ function UIBottomPanel:afterLoad(old, new)
end
self.bank_button = self.buttons[1]:makeToggle()
end
-- Hotfix to force re-calculation of the money font (see issue #1193)
self.money_font = self.ui.app.gfx:loadFont("QData", "Font05V")
self:registerKeyHandlers()
Window.afterLoad(self, old, new)
end

View File

@@ -96,22 +96,29 @@ function UIBuildRoom:UIBuildRoom(ui)
_S.room_classes.clinics,
_S.room_classes.facilities
}
self.category_rooms = {
}
self:updateBuildableRooms()
self:makeTooltip(_S.tooltip.build_room_window.cost, 160, 228, 282, 242)
end
--! Checks what rooms are now available to build
function UIBuildRoom:updateBuildableRooms()
local app = self.ui.app
self.category_rooms = {}
for i, category in ipairs({"diagnosis", "treatment", "clinics", "facilities"}) do
local rooms = {}
self.category_rooms[i] = rooms
for _, room in ipairs(app.world.available_rooms) do
for _, room_disc in pairs(self.ui.hospital.room_discoveries) do
local room = room_disc.room
-- NB: Unimplemented rooms are hidden unless in debug mode
if (app.config.debug or room.class) and room.categories[category] and
ui.hospital.discovered_rooms[room] then
room_disc.is_discovered then
rooms[#rooms + 1] = room
end
end
table.sort(rooms, function(r1, r2) return r1.categories[category] < r2.categories[category] end)
end
self:makeTooltip(_S.tooltip.build_room_window.cost, 160, 228, 282, 242)
end
local cat_label_y = {21, 53, 84, 116}
@@ -145,6 +152,7 @@ function UIBuildRoom:setCategory(index)
self.ui:tutorialStep(3, 3, 2)
end
self.category_index = index
self:updateBuildableRooms()
self.list_title = _S.build_room_window.pick_room_type
self.list = self.category_rooms[index]

View File

@@ -69,6 +69,11 @@ function UIConfirmDialog:UIConfirmDialog(ui, text, callback_ok, callback_cancel)
self:registerKeyHandlers()
end
-- Confirm dialogs are used for errors and require the game to pause
function UIConfirmDialog:mustPause()
return true
end
function UIConfirmDialog:registerKeyHandlers()
self:addKeyHandler("global_confirm", self.ok)
self:addKeyHandler("global_confirm_alt", self.ok)

View File

@@ -186,7 +186,7 @@ function UIEditRoom:cancel()
end
))
else
self:close()
self:abortRoom()
end
self.ui:setCursor(self.ui.default_cursor)
elseif self.phase == "objects" then
@@ -595,9 +595,96 @@ function UIEditRoom:returnToWallPhase(early)
end
end
-- Remove walls
local function remove_wall_line(x, y, step_x, step_y, n_steps, layer, neigh_x, neigh_y, world)
local map = world.map.th
for _ = 1, n_steps do
local existing = map:getCell(x, y, layer)
-- Possibly add transparency.
local flag = 0
if world.ui.transparent_walls then
flag = 1024
end
if world:getWallIdFromBlockId(existing) ~= "external" then
local neighbour = world:getRoom(x + neigh_x, y + neigh_y)
if neighbour then
if neigh_x ~= 0 or neigh_y ~= 0 then
local set = world:getWallSetFromBlockId(existing)
local dir = world:getWallDirFromBlockId(existing)
if set == "inside_tiles" then
set = "outside_tiles"
end
map:setCell(x, y, layer, flag + world.wall_types[neighbour.room_info.wall_type][set][dir])
end
else
map:setCell(x, y, layer, flag)
end
end
x = x + step_x
y = y + step_y
end
end
function UIEditRoom:removeRoom(save_objects, room, world)
-- Remove any placed objects (add them to list again) when save_objects is true
for x = room.x, room.x + room.width - 1 do
for y = room.y, room.y + room.height - 1 do
while true do
-- get litter then any other object
local obj = world:getObject(x, y, 'litter') or world:getObject(x, y)
-- but we need to ignore doors
if not obj or obj == room.door or class.is(obj, SwingDoor) then
break
end
if obj.object_type.id == "litter" then -- Silently remove litter from the world.
obj:remove()
else
if save_objects then
local obj_state = obj:getState()
world:destroyEntity(obj)
if not obj.master then
self:addObjects({{
object = TheApp.objects[obj.object_type.id],
state = obj_state,
qty = 1
}})
end
else
-- just destroy it
world:destroyEntity(obj)
end
end
end
end
end
-- now doors
world:destroyEntity(room.door)
if room.door2 then
world:destroyEntity(room.door2)
end
if save_objects then
-- backup list of objects
self.objects_backup = {}
for k, o in pairs(self.objects) do
self.objects_backup[k] = { object = o.object, qty = o.qty, state = o.state }
end
UIPlaceObjects.removeAllObjects(self, true)
end
remove_wall_line(room.x, room.y, 0, 1, room.height, 3, -1, 0, world)
remove_wall_line(room.x, room.y, 1, 0, room.width , 2, 0, -1, world)
remove_wall_line(room.x + room.width, room.y , 0, 1, room.height, 3, 0, 0, world)
remove_wall_line(room.x, room.y + room.height, 1, 0, room.width , 2, 0, 0, world)
-- Reset floor tiles and flags
world.map.th:unmarkRoom(room.x, room.y, room.width, room.height)
end
function UIEditRoom:returnToDoorPhase()
self.ui:tutorialStep(3, {13, 14, 15}, 9)
local map = self.ui.app.map.th
local room = self.room
room.built = false
if room.door and room.door.queue then
@@ -607,78 +694,7 @@ function UIEditRoom:returnToDoorPhase()
self.purchase_button:enable(false)
self.pickup_button:enable(false)
-- Remove any placed objects (add them to list again)
for x = room.x, room.x + room.width - 1 do
for y = room.y, room.y + room.height - 1 do
while true do
local obj = self.world:getObject(x, y)
if not obj or obj == room.door or class.is(obj, SwingDoor) then
break
end
if obj.object_type.id == "litter" then -- Silently remove litter from the world.
obj:remove()
break
end
local obj_state = obj:getState()
self.world:destroyEntity(obj)
if not obj.master then
self:addObjects({{
object = TheApp.objects[obj.object_type.id],
state = obj_state,
qty = 1
}})
end
end
end
end
self.world:destroyEntity(self.room.door)
if self.room.door2 then
self.world:destroyEntity(self.room.door2)
end
-- backup list of objects
self.objects_backup = {}
for k, o in pairs(self.objects) do
self.objects_backup[k] = { object = o.object, qty = o.qty, state = o.state }
end
UIPlaceObjects.removeAllObjects(self, true)
-- Remove walls
local function remove_wall_line(x, y, step_x, step_y, n_steps, layer, neigh_x, neigh_y)
for _ = 1, n_steps do
local existing = map:getCell(x, y, layer)
-- Possibly add transparency.
local flag = 0
if self.ui.transparent_walls then
flag = 1024
end
if self.world:getWallIdFromBlockId(existing) ~= "external" then
local neighbour = self.world:getRoom(x + neigh_x, y + neigh_y)
if neighbour then
if neigh_x ~= 0 or neigh_y ~= 0 then
local set = self.world:getWallSetFromBlockId(existing)
local dir = self.world:getWallDirFromBlockId(existing)
if set == "inside_tiles" then
set = "outside_tiles"
end
map:setCell(x, y, layer, flag + self.world.wall_types[neighbour.room_info.wall_type][set][dir])
end
else
map:setCell(x, y, layer, flag)
end
end
x = x + step_x
y = y + step_y
end
end
remove_wall_line(room.x, room.y, 0, 1, room.height, 3, -1, 0)
remove_wall_line(room.x, room.y, 1, 0, room.width , 2, 0, -1)
remove_wall_line(room.x + room.width, room.y , 0, 1, room.height, 3, 0, 0)
remove_wall_line(room.x, room.y + room.height, 1, 0, room.width , 2, 0, 0)
-- Reset floor tiles and flags
self.world.map.th:unmarkRoom(room.x, room.y, room.width, room.height)
self:removeRoom(true, room, self.world)
-- Re-create blueprint
local rect = self.blueprint_rect
@@ -864,12 +880,16 @@ function UIEditRoom:enterDoorPhase()
-- check if all adjacent tiles of the rooms are still connected
if not self:checkReachability() then
-- undo passable flags and go back to walls phase
self.phase = "walls"
self:returnToWallPhase(true)
self.ui:playSound("wrong2.wav")
self.ui.adviser:say(_A.room_forbidden_non_reachable_parts)
return
if self.ui.app.config.allow_blocking_off_areas then
print("Blocking off areas is allowed with room " .. self.blueprint_rect.x .. ", " .. self.blueprint_rect.y .. ".")
else
-- undo passable flags and go back to walls phase
self.phase = "walls"
self:returnToWallPhase(true)
self.ui:playSound("wrong2.wav")
self.ui.adviser:say(_A.room_forbidden_non_reachable_parts)
return
end
end
self.desc_text = _S.place_objects_window.place_door
@@ -1198,7 +1218,7 @@ function UIEditRoom:setDoorBlueprint(orig_x, orig_y, orig_wall)
if self.blueprint_door.anim[1] then
-- retrieve the old door position details to reset the blue print
local oldx, oldy
local _, _, oldx_mod, oldy_mod, _ = doorWallOffsetCalculations(self.blueprint_door.floor_x,
local _, _, oldx_mod, oldy_mod, _ = doorWallOffsetCalculations(self.blueprint_door.floor_x,
self.blueprint_door.floor_y, self.blueprint_door.wall)
-- If we're dealing with swing doors the anim variable is actually a table with three
-- identical "doors".

View File

@@ -160,13 +160,14 @@ function UIAnnualReport:UIAnnualReport(ui, world)
table.sort(self.value_sort, desc_order)
table.sort(self.salary_sort, desc_order)
-- Pause the game to allow the player plenty of time to check all statistics and trophies won
if world and not world:isCurrentSpeed("Pause") then
world:setSpeed("Pause")
end
TheApp.video:setBlueFilterActive(false)
end
-- Make sure this window pauses the game, we want to let the player browse their awards
function UIAnnualReport:mustPause()
return true
end
--! Finds out which awards and/or trophies the player has been awarded this year.
function UIAnnualReport:checkTrophiesAndAwards(world)
@@ -200,12 +201,15 @@ function UIAnnualReport:checkTrophiesAndAwards(world)
self.rep_amount = self.rep_amount + win_value
end
-- Impressive Reputation in the year (above a threshold throughout the year)
if hosp.reputation_above_threshold then
if hosp.has_impressive_reputation then
self:addTrophy(_S.trophy_room.consistant_rep.trophies[math.random(1, 2)], "money", prices.TrophyReputationBonus)
self.won_amount = self.won_amount + prices.TrophyReputationBonus
end
-- No deaths or around a 100% Cure rate in the year
if hosp.num_deaths_this_year == 0 then
-- Everyone treated successfully, no deaths, or around a 100% Cure rate in the year
if hosp.num_cured_ty > 1 and hosp.num_deaths_this_year == 0 and hosp.not_cured_ty == 0 then
self:addTrophy(_S.trophy_room.all_cured.trophies[math.random(1, 2)], "money", prices.TrophyAllCuredBonus)
self.won_amount = self.won_amount + prices.TrophyAllCuredBonus
elseif hosp.num_deaths_this_year == 0 then
self:addTrophy(_S.trophy_room.no_deaths.trophies[math.random(1, 3)], "money", prices.TrophyDeathBonus)
self.won_amount = self.won_amount + prices.TrophyDeathBonus
elseif hosp.num_cured_ty > (hosp.not_cured_ty * 0.9) then
@@ -251,7 +255,10 @@ function UIAnnualReport:checkTrophiesAndAwards(world)
end
-- Deaths
if hosp.num_deaths_this_year < prices.DeathsAward then
if hosp.num_cured_ty > 1 and hosp.num_deaths_this_year == 0 and hosp.not_cured_ty == 0 then
self:addAward(_S.trophy_room.no_deaths.awards[1], "money", prices.AllCuresBonus)
self.award_won_amount = self.award_won_amount + prices.AllCuresBonus
elseif hosp.num_deaths_this_year < prices.DeathsAward then
self:addAward(_S.trophy_room.no_deaths.awards[math.random(1, 2)], "money", prices.DeathsBonus)
self.award_won_amount = self.award_won_amount + prices.DeathsBonus
elseif hosp.num_deaths_this_year > prices.DeathsPoor then
@@ -445,17 +452,8 @@ end
--! Overridden close function. The game should be unpaused again when closing the dialog.
function UIAnnualReport:close()
if TheApp.world:getLocalPlayerHospital().game_won then
if not TheApp.world:isCurrentSpeed("Pause") then
TheApp.world:setSpeed("Pause")
TheApp.video:setBlueFilterActive(false)
end
TheApp.video:setBlueFilterActive(false)
TheApp.world.ui.bottom_panel:openLastMessage()
elseif TheApp.world:isCurrentSpeed("Pause") then
if TheApp.ui.speed_up_key_pressed then
TheApp.world:setSpeed("Speed Up")
else
TheApp.world:setSpeed(TheApp.world.prev_speed)
end
end
self:updateAwards()
Window.close(self)

View File

@@ -149,7 +149,6 @@ end
function UICasebook:updateIcons()
local disease = self.selected_disease
local hosp = self.hospital
local world = hosp.world
local known = true
-- Curable / not curable icons and their tooltip
@@ -170,16 +169,14 @@ function UICasebook:updateIcons()
local build = false
local staff = false
-- Room requirements
if #req.rooms > 0 then
for _, room_id in ipairs(req.rooms) do
-- Not researched yet?
if not hosp.discovered_rooms[world.available_rooms[room_id]] then
known = false
research = (research and (research .. ", ") or " (") .. TheApp.rooms[room_id].name
end
-- Researched, but not built. TODO: maybe make this an else clause to not oversize the tooltip that much
build = (build and (build .. ", ") or " (") .. TheApp.rooms[room_id].name
for _, room_id in ipairs(req.rooms) do
-- Not researched yet?
if not hosp:isRoomDiscovered(room_id) then
known = false
research = (research and (research .. ", ") or " (") .. TheApp.rooms[room_id].name
end
-- Researched, but not built. TODO: maybe make this an else clause to not oversize the tooltip that much
build = (build and (build .. ", ") or " (") .. TheApp.rooms[room_id].name
end
research = research and (_S.tooltip.casebook.cure_requirement.research_machine .. research .. "). ") or ""
build = build and (_S.tooltip.casebook.cure_requirement.build_room .. build .. "). ") or ""

View File

@@ -88,6 +88,11 @@ function UIFax:UIFax(ui, icon)
self:addPanel(0, 326, 382):makeButton(0, 0, 44, 11, 13, button("#"))
end
-- Faxes pause the game
function UIFax:mustPause()
return true
end
function UIFax:updateChoices()
local choices = self.message.choices
for i, button in ipairs(self.choice_buttons) do
@@ -173,7 +178,7 @@ function UIFax:choice(choice_number)
end
local vip_ignores_refusal = math.random(1, 2)
if choice == "accept_emergency" then
self.ui.app.world:newObject("helicopter", self.ui.hospital, "north")
self.ui.app.world:newObject("helicopter", "north")
self.ui:addWindow(UIWatch(self.ui, "emergency"))
self.ui:playAnnouncement(self.ui.hospital.emergency.disease.emergency_sound, AnnouncementPriority.Critical)
self.ui.adviser:say(_A.information.emergency)
@@ -204,7 +209,9 @@ function UIFax:choice(choice_number)
-- Set the new salary.
self.ui.hospital.player_salary = self.ui.hospital.salary_offer
if tonumber(self.ui.app.world.map.level_number) then
self.ui.app:loadLevel(self.ui.app.world.map.level_number + 1, self.ui.app.map.difficulty)
local next_level = self.ui.app.world.map.level_number + 1
self.ui.app:loadLevel(next_level, self.ui.app.map.difficulty)
self.ui.app.moviePlayer:playAdvanceMovie(next_level)
else
for i, level in ipairs(self.ui.app.world.campaign_info.levels) do
if self.ui.app.world.map.level_number == level then
@@ -221,6 +228,8 @@ function UIFax:choice(choice_number)
elseif choice == "return_to_main_menu" then
self.ui.app.moviePlayer:playWinMovie()
self.ui.app:loadMainMenu()
elseif choice == "stay_on_level" then
self.ui.hospital.win_declined = true
end
self.icon:removeMessage()
self:close()
@@ -259,8 +268,11 @@ function UIFax:validate()
if not hosp.spawn_rate_cheat then
self.ui.adviser:say(_A.cheats.roujin_on_cheat)
hosp.spawn_rate_cheat = true
self:cheatByFax()
else
self.ui.adviser:say(_A.cheats.roujin_off_cheat)
-- Clear the current month's spawns to give the player a break
self.ui.app.world.spawn_dates = {}
hosp.spawn_rate_cheat = nil
end
else
@@ -273,18 +285,24 @@ function UIFax:validate()
-- TODO: Other cheats (preferably with slight obfuscation, as above)
end
function UIFax:cheatByFax()
local cheatWindow = self.ui:getWindow(UICheats)
local cheat = self.ui.hospital.hosp_cheats
cheat:announceCheat()
-- If a cheats window is open, make sure the UI is updated
if cheatWindow then
cheatWindow:updateCheatedStatus()
end
end
function UIFax:appendNumber(number)
self.code = self.code .. number
end
function UIFax:close()
local world = self.ui.app.world
self.icon.fax = nil
self.icon:adjustToggle()
UIFullscreen.close(self)
if world and world:isCurrentSpeed("Pause") then
world:setSpeed(world.prev_speed)
end
end
function UIFax:afterLoad(old, new)

View File

@@ -174,9 +174,8 @@ function UIStaffManagement:updateStaffList(staff_member_removed)
Handyman = {},
Receptionist = {},
}
staff_members.Surgeon = staff_members.Doctor
for _, staff in ipairs(hosp.staff) do
local list = staff_members[staff.humanoid_class]
local list = staff_members[staff.profile.humanoid_class]
list[#list + 1] = staff
-- The selected staff might have been moved because someone else was removed from the list.
if selected_staff == staff then
@@ -225,7 +224,7 @@ end
-- Function to select a given staff member.
-- Includes switching to correct category and page.
function UIStaffManagement:selectStaff(staff)
self:setCategory(staff.humanoid_class == "Surgeon" and "Doctor" or staff.humanoid_class)
self:setCategory(staff.profile.humanoid_class)
for i, s in ipairs(self.staff_members[self.category]) do
if s == staff then
self:selectIndex(i)

View File

@@ -112,8 +112,9 @@ function UITownMap:onMouseMove(x, y)
local tx = math.floor((x - 227) / 3)
local ty = math.floor((y - 25) / 3)
self.hover_plot = nil
if 0 <= tx and tx < 128 and 0 <= ty and ty < 128 then
local map = self.ui.hospital.world.map.th
local map = self.ui.hospital.world.map.th
local width, height = map:size()
if 0 <= tx and tx < width and 0 <= ty and ty < height then
self.hover_plot = map:getCellFlags(tx + 1, ty + 1, flag_cache).parcelId
end
return UIFullscreen.onMouseMove(self, x, y)
@@ -124,8 +125,9 @@ function UITownMap:onMouseUp(button, x, y)
if button == "left" then
local tx = math.floor((x - 227) / 3)
local ty = math.floor((y - 25) / 3)
if 0 <= tx and tx < 128 and 0 <= ty and ty < 128 then
local map = self.ui.hospital.world.map.th
local map = self.ui.hospital.world.map.th
local width, height = map:size()
if 0 <= tx and tx < width and 0 <= ty and ty < height then
local plot = map:getCellFlags(tx + 1, ty + 1, flag_cache).parcelId
if plot ~= 0 then
if self.ui.hospital:purchasePlot(plot) then
@@ -140,7 +142,8 @@ function UITownMap:onMouseUp(button, x, y)
local tx = math.floor((x - 227) / 3)
local ty = math.floor((y - 25) / 3)
local map = self.ui.hospital.world.map.th
if 0 <= tx and tx < 128 and 0 <= ty and ty < 128 then
local width, height = map:size()
if 0 <= tx and tx < width and 0 <= ty and ty < height then
local plot = map:getCellFlags(tx + 1, ty + 1, flag_cache).parcelId
if plot ~= 0 then
local sx, sy = self.ui.app.map:WorldToScreen(tx, ty)
@@ -169,15 +172,15 @@ function UITownMap:draw(canvas, x, y)
config = self:initRuntimeConfig()
end
-- We need to draw number of people, plants, fire extinguisers, other objects
-- We need to draw number of people, plants, fire extinguishers, other objects
-- and radiators.
-- NB: original TH's patient count was always 1 too big (started counting at 1)
-- This is likely a bug and we do not copy this behavior.
local patientcount = hospital.patientcount
local plants = world.object_counts.plant
local fireext = world.object_counts.extinguisher
local objs = world.object_counts.general
local radiators = world.object_counts.radiator
local patientcount = hospital:countPatients()
local plants = hospital:countPlants()
local fireext = hospital:countFireExtinguishers()
local objs = hospital:countGeneralObjects()
local radiators = hospital:countRadiators()
self.info_font:draw(canvas, patientcount, x + 95, y + 57)
self.info_font:draw(canvas, plants, x + 95, y + 110)
@@ -186,7 +189,7 @@ function UITownMap:draw(canvas, x, y)
self.info_font:draw(canvas, radiators, x + 95, y + 265)
-- Heating costs
local heating_costs = math.floor(((hospital.radiator_heat *10)* radiators)* 7.5)
local heating_costs = math.floor(((hospital.heating.radiator_heat *10)* radiators)* 7.5)
self.info_font:draw(canvas, ("%8i"):format(heating_costs), x + 100, y + 355)
-- draw money balance
@@ -194,7 +197,7 @@ function UITownMap:draw(canvas, x, y)
-- radiator heat
local rad_max_width = 60 -- Radiator indicator width
local rad_width = rad_max_width * hospital.radiator_heat
local rad_width = rad_max_width * hospital.heating.radiator_heat
for dx = 0, rad_width do
self.panel_sprites:draw(canvas, 9, x + 101 + dx, y + 319)
end
@@ -314,25 +317,19 @@ end
function UITownMap:decreaseHeat()
local h = self.ui.hospital
local heat = math.floor(h.radiator_heat * 10 + 0.5)
local heat = math.floor(h.heating.radiator_heat * 10 + 0.5)
if not h.heating_broke then
heat = heat - 1
if heat < 1 then
heat = 1
end
h.radiator_heat = heat / 10
heat = math.max(heat - 1, 1)
h.heating.radiator_heat = heat / 10
end
end
function UITownMap:increaseHeat()
local h = self.ui.hospital
local heat = math.floor(h.radiator_heat * 10 + 0.5)
local heat = math.floor(h.heating.radiator_heat * 10 + 0.5)
if not h.heating_broke then
heat = heat + 1
if heat > 10 then
heat = 10
end
h.radiator_heat = heat / 10
heat = math.min(heat + 1, 10)
h.heating.radiator_heat = heat / 10
end
end

View File

@@ -66,10 +66,10 @@ function UIJukebox:UIJukebox(app)
end
self.track_buttons[i] = self:addPanel(404, self.width - 61, y):makeToggleButton(19, 4, 24, 24, 405):setSound("selectx.wav")
if not info.enabled then
self.track_buttons[i]:toggle()
self.track_buttons[i]:setToggleState(true)
end
self.track_buttons[i].on_click = --[[persistable:jukebox_toggle_track]] function(window, off)
window:toggleTrack(i, info, not off)
self.track_buttons[i].on_click = --[[persistable:jukebox_toggle_track]] function(window)
window:toggleTrack(i)
end
end
@@ -116,9 +116,11 @@ function UIJukebox:stopBackgroundTrack()
self.audio:stopBackgroundTrack()
end
function UIJukebox:toggleTrack(index, info, on)
info.enabled = on
if not on and self.audio.background_music == info.music then
function UIJukebox:toggleTrack(index)
local info = self.audio.background_playlist[index]
self.track_buttons[index]:setToggleState(info.enabled)
info.enabled = not info.enabled
if not info.enabled and self.audio.background_music == info.music then
self.audio:stopBackgroundTrack()
self.audio:playRandomBackgroundTrack()
end
@@ -127,6 +129,7 @@ end
function UIJukebox:loopTrack()
local index = self.audio:findIndexOfCurrentTrack()
local playlist = self.audio.background_playlist
if not playlist[index] then return end
if playlist[index].loop then
playlist[index].loop = false
@@ -134,8 +137,7 @@ function UIJukebox:loopTrack()
for i, list_entry in ipairs(playlist) do
if list_entry.enabled_before_loop and index ~= i then
list_entry.enabled_before_loop = nil
self:toggleTrack(i, list_entry, true)
self.track_buttons[i]:toggle()
self:toggleTrack(i)
end
end
else
@@ -144,14 +146,16 @@ function UIJukebox:loopTrack()
for i, list_entry in ipairs(playlist) do
if list_entry.enabled and index ~= i then
list_entry.enabled_before_loop = true
self:toggleTrack(i, list_entry, false)
self.track_buttons[i]:toggle()
self:toggleTrack(i)
end
end
end
end
function UIJukebox:draw(canvas, x, y)
for i, info in ipairs(TheApp.audio.background_playlist) do
self.track_buttons[i]:setToggleState(not info.enabled)
end
Window.draw(self, canvas, x, y)
x, y = self.x + x, self.y + y
@@ -172,3 +176,10 @@ function UIJukebox:draw(canvas, x, y)
end
end
end
function UIJukebox:afterLoad(old, new)
if not (self.app.config.audio and self.app:isAudioEnabled()) then
self:close()
end
Window.afterLoad(self, old, new)
end

View File

@@ -71,6 +71,7 @@ function UIMenuBar:onTick()
self.menu_disappear_counter = nil
else
if self.menu_disappear_counter == 0 then
self.ui.app:saveConfig()
self.menu_disappear_counter = nil
local close_to = self.active_menu and self.active_menu.level or 0
for i = #self.open_menus, close_to + 1, -1 do
@@ -577,21 +578,18 @@ function UIMenuBar:makeGameMenu(app)
return level == app.config.music_volume,
function()
app.audio:setBackgroundVolume(level)
app:saveConfig()
end,
""
elseif setting == "sound" then
return level == app.config.sound_volume,
function()
app.audio:setSoundVolume(level)
app:saveConfig()
end,
""
else
return level == app.config.announcement_volume,
function()
app.audio:setAnnouncementVolume(level)
app:saveConfig()
end,
""
end
@@ -609,7 +607,6 @@ function UIMenuBar:makeGameMenu(app)
app.config.play_sounds,
function(item)
app.audio:playSoundEffects(item.checked)
app:saveConfig()
end,
nil,
function()
@@ -621,7 +618,6 @@ function UIMenuBar:makeGameMenu(app)
app.config.play_announcements,
function(item)
app.config.play_announcements = item.checked
app:saveConfig()
end,
nil,
function()
@@ -633,7 +629,6 @@ function UIMenuBar:makeGameMenu(app)
function(item)
app.config.play_music = item.checked
self.ui:togglePlayMusic(item)
app:saveConfig()
end,
nil,
function(musicStatus)
@@ -665,7 +660,6 @@ function UIMenuBar:makeGameMenu(app)
options:appendCheckItem(_S.menu_options.capture_mouse,
app.config.capture_mouse,
function(item) app.config.capture_mouse = item.checked
app:saveConfig()
app:setCaptureMouse()
end)
@@ -673,7 +667,6 @@ function UIMenuBar:makeGameMenu(app)
not app.config.adviser_disabled,
function(item)
app.config.adviser_disabled = not item.checked
app:saveConfig()
end,
nil,
function()
@@ -684,7 +677,6 @@ function UIMenuBar:makeGameMenu(app)
app.config.twentyfour_hour_clock,
function(item)
app.config.twentyfour_hour_clock = item.checked
app:saveConfig()
end)
local function temperatureDisplay(method)
@@ -756,6 +748,9 @@ function UIMenuBar:makeGameMenu(app)
local function disable_salary_raise(item)
app.world:debugDisableSalaryRaise(item.checked)
end
local function allowBlockingAreas(item)
app.config.allow_blocking_off_areas = item.checked
end
local function overlay(...)
local args = {n = select('#', ...), ...}
return function(item, m)
@@ -770,7 +765,9 @@ function UIMenuBar:makeGameMenu(app)
for L = 1, 12 do
levels_menu:appendItem((" L%i "):format(L), function()
local status, err = pcall(app.loadLevel, app, L)
if not status then
if status then
self.ui.app.moviePlayer:playAdvanceMovie(L)
else
err = _S.errors.load_prefix .. err
print(err)
self.ui:addWindow(UIInformation(self.ui, {err}))
@@ -783,6 +780,7 @@ function UIMenuBar:makeGameMenu(app)
:appendItem(_S.menu_debug.connect_debugger:format(hotkey_value_label("global_connectDebugger", hotkeys)), function() self.ui:connectDebugger() end)
:appendCheckItem(_S.menu_debug.limit_camera, true, limit_camera, nil, function() return self.ui.limit_to_visible_diamond end)
:appendCheckItem(_S.menu_debug.disable_salary_raise, false, disable_salary_raise, nil, function() return self.ui.app.world.debug_disable_salary_raise end)
:appendCheckItem(_S.menu_debug.allow_blocking_off_areas, false, allowBlockingAreas, nil, function() return self.ui.app.config.allow_blocking_off_areas end)
:appendItem(_S.menu_debug.make_debug_fax, function() self.ui:makeDebugFax() end)
:appendItem(_S.menu_debug.make_debug_patient, function() self.ui:addWindow(UIMakeDebugPatient(self.ui)) end)
:appendItem(_S.menu_debug.cheats:format(hotkey_value_label("ingame_showCheatWindow", hotkeys)), function() self.ui:addWindow(UICheats(self.ui)) end)

View File

@@ -104,13 +104,16 @@ function UIMessage:adjustToggle()
end
end
--! Displays the fax/strike message to the player when opened from the bottom_panel.
function UIMessage:openMessage()
if TheApp.world:isUserActionProhibited() and not self.ui:checkForMustPauseWindows() then
self.ui:playSound("wrong2.wav")
self:adjustToggle()
return
end
if TheApp.world:isCurrentSpeed("Speed Up") then
TheApp.world:previousSpeed()
end
if not TheApp.world:isCurrentSpeed("Pause") then
TheApp.world:setSpeed("Pause")
end
if self.type == "strike" then -- strikes are special cases, as they are not faxes
self.ui:addWindow(UIStaffRise(self.ui, self.owner, self.message))
self:removeMessage()

View File

@@ -697,7 +697,13 @@ function UIPlaceObjects:setBlueprintCell(x, y)
end
if self.object_anim and object.class ~= "SideObject" then
if allgood then
allgood = not world:wouldNonSideObjectBreakPathfindingIfSpawnedAt(x, y, object, self.object_orientation, roomId)
if world:wouldNonSideObjectBreakPathfindingIfSpawnedAt(x, y, object, self.object_orientation, roomId) then
if self.ui.app.config.allow_blocking_off_areas then
print("Blocking off areas is allowed at " .. x .. ", " .. y .. ".")
else
allgood = false
end
end
end
if ATTACH_BLUEPRINT_TO_TILE then
self.object_anim:setTile(map, x, y)
@@ -721,7 +727,11 @@ function UIPlaceObjects:setBlueprintCell(x, y)
if not world.pathfinder:findDistance(x, y, checked_x, checked_y) then
--we need to check if the failure to get the distance is due to the presence of an object in the adjacent tile
if map:getCellFlags(checked_x, checked_y)["passable"] then
allgood = false
if self.ui.app.config.allow_blocking_off_areas then
print("Blocking off areas is allowed at " .. x .. ", " .. y .. ".")
else
allgood = false
end
end
end
flags[passable_flag] = true

View File

@@ -77,15 +77,34 @@ end
local flag_cache = {}
local flag_altpal = 16
--! Private function. Is the staff member (self) permitted
-- to be placed where the user is hovering or clicking?
function UIPlaceStaff:_isValidStaffPlacement()
local world, x, y = self.world, self.tile_x, self.tile_y
world.map.th:getCellFlags(x, y, flag_cache)
-- Is the tile inside the player's hospital building?
if not (self.ui.hospital:getPlayerIndex() == flag_cache.owner and
flag_cache.hospital) then
return false
end
local room = world:getRoom(x, y)
-- On a tile humanoids can walk on?
local walkable = flag_cache.passable and (not room and true or not room.crashed)
-- and in an area the regular staff (doctors, nurses and handymen) can go?
local staffable = (self.allow_in_rooms or flag_cache.roomId == 0)
-- Or is it a receptionist placed on an unstaffed reception desk?
local reception = false
if self.profile.humanoid_class == "Receptionist" then
local desk = world:getObject(x, y, "reception_desk") or
world:findObjectNear(self, "reception_desk", 0)
reception = desk and not desk.receptionist
end
return (walkable and staffable) or reception
end
function UIPlaceStaff:draw(canvas)
if self.world.user_actions_allowed then
self.world.map.th:getCellFlags(self.tile_x, self.tile_y, flag_cache)
local room = self.world:getRoom(self.tile_x, self.tile_y)
local player_id = self.ui.hospital:getPlayerIndex()
local valid = flag_cache.hospital and flag_cache.passable and
(self.allow_in_rooms or flag_cache.roomId == 0) and
(not room and true or not room.crashed) and
flag_cache.owner == player_id
local valid = self:_isValidStaffPlacement()
self.anim:setFlag(valid and 0 or flag_altpal)
local zoom = self.ui.zoom_factor
if canvas:scale(zoom) then
@@ -107,13 +126,7 @@ function UIPlaceStaff:onMouseUp(button, x, y)
return true
elseif button == "left" then
self:onMouseMove(x, y)
self.world.map.th:getCellFlags(self.tile_x, self.tile_y, flag_cache)
local room = self.world:getRoom(self.tile_x, self.tile_y)
local player_id = self.ui.hospital:getPlayerIndex()
if flag_cache.hospital and flag_cache.passable and
(self.allow_in_rooms or flag_cache.roomId == 0) and
(not room or not room.crashed) and
flag_cache.owner == player_id then
if self:_isValidStaffPlacement() then
if self.staff then
self.staff:setTile(self.tile_x, self.tile_y)
else

View File

@@ -1,246 +0,0 @@
--[[ Copyright (c) 2010 Manuel "Roujin" Wolf
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. --]]
--! A dialog for activating cheats
class "UICheats" (UIResizable)
---@type UICheats
local UICheats = _G["UICheats"]
local col_bg = {
red = 154,
green = 146,
blue = 198,
}
local col_caption = {
red = 174,
green = 166,
blue = 218,
}
local col_border = {
red = 134,
green = 126,
blue = 178,
}
local col_cheated_no = {
red = 36,
green = 154,
blue = 36,
}
local col_cheated_yes = {
red = 224,
green = 36,
blue = 36,
}
--[[ Constructs the cheat dialog.
!param ui (UI) The active ui.
]]
function UICheats:UICheats(ui)
self.cheats = {
{name = "money", func = self.cheatMoney},
{name = "all_research", func = self.cheatResearch},
{name = "emergency", func = self.cheatEmergency},
{name = "epidemic", func = self.cheatEpidemic},
{name = "toggle_infected", func = self.cheatToggleInfected},
{name = "vip", func = self.cheatVip},
{name = "earthquake", func = self.cheatEarthquake},
{name = "create_patient", func = self.cheatPatient},
{name = "end_month", func = self.cheatMonth},
{name = "end_year", func = self.cheatYear},
{name = "lose_level", func = self.cheatLose},
{name = "win_level", func = self.cheatWin},
{name = "increase_prices", func = self.cheatIncreasePrices},
{name = "decrease_prices", func = self.cheatDecreasePrices},
}
self:UIResizable(ui, 300, 200, col_bg)
self.default_button_sound = "selectx.wav"
self.modal_class = "cheats"
self.esc_closes = true
self.resizable = false
self:setDefaultPosition(0.2, 0.4)
local y = 10
self:addBevelPanel(20, y, 260, 20, col_caption):setLabel(_S.cheats_window.caption)
.lowered = true
y = y + 30
self:addColourPanel(20, y, 260, 40, col_bg.red, col_bg.green, col_bg.blue):setLabel({_S.cheats_window.warning})
y = y + 40
self.cheated_panel = self:addBevelPanel(20, y, 260, 18, col_cheated_no, col_border, col_border)
local function button_clicked(num)
return --[[persistable:cheats_button]] function(window)
window:buttonClicked(num)
end
end
self.item_panels = {}
self.item_buttons = {}
y = y + 30
for num = 1, #self.cheats do
self.item_panels[num] = self:addBevelPanel(20, y, 260, 20, col_bg)
:setLabel(_S.cheats_window.cheats[self.cheats[num].name])
self.item_buttons[num] = self.item_panels[num]:makeButton(0, 0, 260, 20, nil, button_clicked(num))
:setTooltip(_S.tooltip.cheats_window.cheats[self.cheats[num].name])
y = y + 20
end
y = y + 20
self:addBevelPanel(20, y, 260, 40, col_bg):setLabel(_S.cheats_window.close)
:makeButton(0, 0, 260, 40, nil, self.buttonBack):setTooltip(_S.tooltip.cheats_window.close)
y = y + 60
self:setSize(300, y)
self:updateCheatedStatus()
end
function UICheats:updateCheatedStatus()
local cheated = self.ui.hospital.cheated
self.cheated_panel:setLabel(cheated and _S.cheats_window.cheated.yes or _S.cheats_window.cheated.no)
self.cheated_panel:setColour(cheated and col_cheated_yes or col_cheated_no)
end
function UICheats:buttonClicked(num)
-- Only the cheats that may fail return false in that case. All others return nothing.
if self.cheats[num].func(self) ~= false then
if self.cheats[num].name ~= "lose_level" then
local announcements = self.ui.app.world.cheat_announcements
if announcements then
self.ui:playSound(announcements[math.random(1, #announcements)])
end
self.ui.hospital.cheated = true
self:updateCheatedStatus()
end
else
-- It was not possible to use this cheat.
self.ui:addWindow(UIInformation(self.ui, {_S.information.cheat_not_possible}))
end
end
function UICheats:cheatMoney()
self.ui.hospital:receiveMoney(10000, _S.transactions.cheat)
end
function UICheats:cheatResearch()
local hosp = self.ui.hospital
for _, cat in ipairs({"diagnosis", "cure"}) do
while hosp.research.research_policy[cat].current do
hosp.research:discoverObject(hosp.research.research_policy[cat].current)
end
end
end
function UICheats:cheatEmergency()
if not self.ui.hospital:createEmergency() then
self.ui:addWindow(UIInformation(self.ui, {_S.misc.no_heliport}))
end
end
--[[ Creates a new contagious patient in the hospital - potentially an epidemic]]
function UICheats:cheatEpidemic()
self.ui.hospital:spawnContagiousPatient()
end
--[[ Before an epidemic has been revealed toggle the infected icons
to easily distinguish the infected patients -- will toggle icons
for ALL future epidemics you cannot distinguish between epidemics
by disease ]]
function UICheats:cheatToggleInfected()
local hospital = self.ui.hospital
if hospital.future_epidemics_pool and #hospital.future_epidemics_pool > 0 then
for _, future_epidemic in ipairs(hospital.future_epidemics_pool) do
local show_mood = future_epidemic.cheat_always_show_mood
future_epidemic.cheat_always_show_mood = not show_mood
local mood_action = show_mood and "deactivate" or "activate"
for _, patient in ipairs(future_epidemic.infected_patients) do
patient:setMood("epidemy4",mood_action)
end
end
else
print("Unable to toggle icons - no epidemics in progress that are not revealed")
end
end
function UICheats:cheatVip()
self.ui.hospital:createVip()
end
function UICheats:cheatEarthquake()
return self.ui.app.world:createEarthquake()
end
function UICheats:cheatPatient()
self.ui.app.world:spawnPatient()
end
function UICheats:cheatMonth()
self.ui.app.world:setEndMonth()
end
function UICheats:cheatYear()
self.ui.app.world:setEndYear()
end
function UICheats:cheatLose()
self.ui.app.world:loseGame(1) -- TODO adjust for multiplayer
end
function UICheats:cheatWin()
self.ui.app.world:winGame(1) -- TODO adjust for multiplayer
end
function UICheats:cheatIncreasePrices()
local hosp = self.ui.app.world.hospitals[1]
for _, casebook in pairs(hosp.disease_casebook) do
local new_price = casebook.price + 0.5
if new_price > 2 then
casebook.price = 2
else
casebook.price = new_price
end
end
end
function UICheats:cheatDecreasePrices()
local hosp = self.ui.app.world.hospitals[1]
for _, casebook in pairs(hosp.disease_casebook) do
local new_price = casebook.price - 0.5
if new_price < 0.5 then
casebook.price = 0.5
else
casebook.price = new_price
end
end
end
function UICheats:buttonBack()
self:close()
end

View File

@@ -0,0 +1,140 @@
--[[ Copyright (c) 2010 Manuel "Roujin" Wolf
Copyright (c) 2020 lewri
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. --]]
--! A dialog for activating cheats
class "UICheats" (UIResizable)
---@type UICheats
local UICheats = _G["UICheats"]
local col_bg = {
red = 154,
green = 146,
blue = 198,
}
local col_caption = {
red = 174,
green = 166,
blue = 218,
}
local col_border = {
red = 134,
green = 126,
blue = 178,
}
local col_cheated_no = {
red = 36,
green = 154,
blue = 36,
}
local col_cheated_yes = {
red = 224,
green = 36,
blue = 36,
}
--[[ Constructs the cheat dialog.
!param ui (UI) The active ui.
]]
function UICheats:UICheats(ui)
self.cheats = ui.hospital.hosp_cheats
self.cheat_list = ui.hospital.hosp_cheats.cheat_list
self:UIResizable(ui, 300, 200, col_bg)
self.default_button_sound = "selectx.wav"
self.modal_class = "cheats"
self.esc_closes = true
self.resizable = false
self:setDefaultPosition(0.2, 0.4)
local y = 10
self:addBevelPanel(20, y, 260, 20, col_caption):setLabel(_S.cheats_window.caption)
.lowered = true
y = y + 30
self:addColourPanel(20, y, 260, 40, col_bg.red, col_bg.green, col_bg.blue):setLabel({_S.cheats_window.warning})
y = y + 40
self.cheated_panel = self:addBevelPanel(20, y, 260, 18, col_cheated_no, col_border, col_border)
local function button_clicked(num)
return --[[persistable:cheats_button]] function(window)
window:buttonClicked(num)
end
end
self.item_panels = {}
self.item_buttons = {}
y = y + 30
for num = 1, #self.cheat_list do
self.item_panels[num] = self:addBevelPanel(20, y, 260, 20, col_bg)
:setLabel(_S.cheats_window.cheats[self.cheat_list[num].name])
self.item_buttons[num] = self.item_panels[num]:makeButton(0, 0, 260, 20, nil, button_clicked(num))
:setTooltip(_S.tooltip.cheats_window.cheats[self.cheat_list[num].name])
y = y + 20
end
y = y + 20
self:addBevelPanel(20, y, 260, 40, col_bg):setLabel(_S.cheats_window.close)
:makeButton(0, 0, 260, 40, nil, self.buttonBack):setTooltip(_S.tooltip.cheats_window.close)
y = y + 60
self:setSize(300, y)
self:updateCheatedStatus()
end
function UICheats:updateCheatedStatus()
local cheated = self.ui.hospital.cheated
self.cheated_panel:setLabel(cheated and _S.cheats_window.cheated.yes or _S.cheats_window.cheated.no)
self.cheated_panel:setColour(cheated and col_cheated_yes or col_cheated_no)
end
function UICheats:buttonClicked(num)
if self.cheats:performCheat(num) then
self.cheats.announceCheat(self.ui)
self:updateCheatedStatus()
else
self.ui:addWindow(UIInformation(self.ui, {_S.information.cheat_not_possible}))
end
end
function UICheats:buttonBack()
self:close()
end
function UICheats:afterLoad(old, new)
if old < 145 then
-- Window must be closed if open for compatibility
local cheatWindow = self.ui:getWindow(UICheats)
if cheatWindow then
cheatWindow:close()
end
end
UIResizable.afterLoad(self, old, new)
end

View File

@@ -43,7 +43,7 @@ local col_caption = {
}
function UICustomise:UICustomise(ui, mode)
self:UIResizable(ui, 320, 280, col_bg)
self:UIResizable(ui, 340, 300, col_bg)
local app = ui.app
self.mode = mode
@@ -57,68 +57,79 @@ function UICustomise:UICustomise(ui, mode)
-- Window parts definition
-- Title
self:addBevelPanel(80, 10, 160, 20, col_caption):setLabel(_S.customise_window.caption)
self:addBevelPanel(85, 10, 170, 20, col_caption):setLabel(_S.customise_window.caption)
.lowered = true
-- Movies, global switch
self:addBevelPanel(20, 40, 135, 20, col_shadow, col_bg, col_bg)
local audio_status = app:isAudioEnabled()
self:addBevelPanel(15, 40, 165, 20, col_shadow, col_bg, col_bg)
:setLabel(_S.customise_window.movies):setTooltip(_S.tooltip.customise_window.movies).lowered = true
self.movies_panel =
self:addBevelPanel(160, 40, 135, 20, col_bg):setLabel(app.config.movies and _S.customise_window.option_on or _S.customise_window.option_off)
self:addBevelPanel(185, 40, 140, 20, col_bg):setLabel(app.config.movies and audio_status and _S.customise_window.option_on or _S.customise_window.option_off)
self.movies_button = self.movies_panel:makeToggleButton(0, 0, 140, 20, nil, self.buttonMoviesGlobal)
:setToggleState(app.config.movies):setTooltip(_S.tooltip.customise_window.movies)
:setToggleState(app.config.movies and audio_status):setTooltip(_S.tooltip.customise_window.movies)
self.movies_button.enabled = audio_status
-- Intro movie
self:addBevelPanel(20, 65, 135, 20, col_shadow, col_bg, col_bg)
self:addBevelPanel(15, 65, 165, 20, col_shadow, col_bg, col_bg)
:setLabel(_S.customise_window.intro):setTooltip(_S.tooltip.customise_window.intro).lowered = true
self.intro_panel =
self:addBevelPanel(160, 65, 135, 20, col_bg):setLabel(app.config.play_intro and _S.customise_window.option_on or _S.customise_window.option_off)
self:addBevelPanel(185, 65, 140, 20, col_bg):setLabel(app.config.play_intro and audio_status and _S.customise_window.option_on or _S.customise_window.option_off)
self.intro_button = self.intro_panel:makeToggleButton(0, 0, 140, 20, nil, self.buttonIntro)
:setToggleState(app.config.play_intro):setTooltip(_S.tooltip.customise_window.intro)
:setToggleState(app.config.play_intro and audio_status):setTooltip(_S.tooltip.customise_window.intro)
self.intro_button.enabled = audio_status
-- Allow user actions when paused
self:addBevelPanel(20, 90, 135, 20, col_shadow, col_bg, col_bg)
self:addBevelPanel(15, 90, 165, 20, col_shadow, col_bg, col_bg)
:setLabel(_S.customise_window.paused):setTooltip(_S.tooltip.customise_window.paused).lowered = true
self.paused_panel =
self:addBevelPanel(160, 90, 135, 20, col_bg):setLabel(app.config.allow_user_actions_while_paused and _S.customise_window.option_on or _S.customise_window.option_off)
self:addBevelPanel(185, 90, 140, 20, col_bg):setLabel(app.config.allow_user_actions_while_paused and _S.customise_window.option_on or _S.customise_window.option_off)
self.paused_button = self.paused_panel:makeToggleButton(0, 0, 140, 20, nil, self.buttonPaused)
:setToggleState(app.config.allow_user_actions_while_paused):setTooltip(_S.tooltip.customise_window.paused)
-- Volume down is opening casebook
self:addBevelPanel(20, 115, 135, 20, col_shadow, col_bg, col_bg)
self:addBevelPanel(15, 115, 165, 20, col_shadow, col_bg, col_bg)
:setLabel(_S.customise_window.volume):setTooltip(_S.tooltip.customise_window.volume).lowered = true
self.volume_panel =
self:addBevelPanel(160, 115, 135, 20, col_bg):setLabel(app.config.volume_opens_casebook and _S.customise_window.option_on or _S.customise_window.option_off)
self:addBevelPanel(185, 115, 140, 20, col_bg):setLabel(app.config.volume_opens_casebook and _S.customise_window.option_on or _S.customise_window.option_off)
self.volume_button = self.volume_panel:makeToggleButton(0, 0, 140, 20, nil, self.buttonVolume)
:setToggleState(app.config.volume_opens_casebook):setTooltip(_S.tooltip.customise_window.volume)
-- Alien DNA from emergencies only/must stand/can knock on doors
self:addBevelPanel(20, 140, 135, 20, col_shadow, col_bg, col_bg)
self:addBevelPanel(15, 140, 165, 20, col_shadow, col_bg, col_bg)
:setLabel(_S.customise_window.aliens):setTooltip(_S.tooltip.customise_window.aliens).lowered = true
self.aliens_panel =
self:addBevelPanel(160, 140, 135, 20, col_bg):setLabel(app.config.alien_dna_only_by_emergency and _S.customise_window.option_on or _S.customise_window.option_off)
self:addBevelPanel(185, 140, 140, 20, col_bg):setLabel(app.config.alien_dna_only_by_emergency and _S.customise_window.option_on or _S.customise_window.option_off)
self.aliens_button = self.aliens_panel:makeToggleButton(0, 0, 140, 20, nil, self.buttonAliens)
:setToggleState(app.config.alien_dna_only_by_emergency):setTooltip(_S.tooltip.customise_window.aliens)
-- Allow female patients with Fractured Bones
self:addBevelPanel(20, 165, 135, 20, col_shadow, col_bg, col_bg)
self:addBevelPanel(15, 165, 165, 20, col_shadow, col_bg, col_bg)
:setLabel(_S.customise_window.fractured_bones):setTooltip(_S.tooltip.customise_window.fractured_bones).lowered = true
self.fractured_bones_panel =
self:addBevelPanel(160, 165, 135, 20, col_bg):setLabel(app.config.disable_fractured_bones_females and _S.customise_window.option_on or _S.customise_window.option_off)
self:addBevelPanel(185, 165, 140, 20, col_bg):setLabel(app.config.disable_fractured_bones_females and _S.customise_window.option_on or _S.customise_window.option_off)
self.fractured_bones_button = self.fractured_bones_panel:makeToggleButton(0, 0, 140, 20, nil, self.buttonFractured_bones)
:setToggleState(app.config.disable_fractured_bones_females):setTooltip(_S.tooltip.customise_window.fractured_bones)
-- Allow average contents when building rooms
self:addBevelPanel(20, 190, 135, 20, col_shadow, col_bg, col_bg)
self:addBevelPanel(15, 190, 165, 20, col_shadow, col_bg, col_bg)
:setLabel(_S.customise_window.average_contents):setTooltip(_S.tooltip.customise_window.average_contents).lowered = true
self.average_contents_panel =
self:addBevelPanel(160, 190, 135, 20, col_bg):setLabel(app.config.enable_avg_contents and _S.customise_window.option_on or _S.customise_window.option_off)
self:addBevelPanel(185, 190, 140, 20, col_bg):setLabel(app.config.enable_avg_contents and _S.customise_window.option_on or _S.customise_window.option_off)
self.average_contents_button = self.average_contents_panel:makeToggleButton(0, 0, 140, 20, nil, self.buttonAverage_contents)
:setToggleState(app.config.enable_avg_contents):setTooltip(_S.tooltip.customise_window.average_contents)
-- Allow removal of destroyed rooms
self:addBevelPanel(15, 215, 165, 20, col_shadow, col_bg, col_bg)
:setLabel(_S.customise_window.remove_destroyed_rooms):setTooltip(_S.tooltip.customise_window.remove_destroyed_rooms).lowered = true
self.destroyed_rooms_panel =
self:addBevelPanel(185, 215, 140, 20, col_bg):setLabel(app.config.remove_destroyed_rooms and _S.customise_window.option_on or _S.customise_window.option_off)
self.destroyed_rooms_button = self.destroyed_rooms_panel:makeToggleButton(0, 0, 140, 20, nil, self.buttonDestroyed_rooms)
:setToggleState(app.config.remove_destroyed_rooms):setTooltip(_S.tooltip.customise_window.remove_destroyed_rooms)
-- "Back" button
self:addBevelPanel(20, 220, 280, 40, col_bg):setLabel(_S.customise_window.back)
:makeButton(0, 0, 280, 40, nil, self.buttonBack):setTooltip(_S.tooltip.customise_window.back)
self:addBevelPanel(15, 245, 310, 40, col_bg):setLabel(_S.customise_window.back)
:makeButton(0, 0, 310, 40, nil, self.buttonBack):setTooltip(_S.tooltip.customise_window.back)
end
function UICustomise:buttonAudioGlobal(checked)
@@ -195,6 +206,15 @@ function UICustomise:buttonAverage_contents(checked)
self:reload()
end
function UICustomise:buttonDestroyed_rooms(checked)
local app = self.ui.app
app.config.remove_destroyed_rooms = not app.config.remove_destroyed_rooms
self.destroyed_rooms_button:toggle()
self.destroyed_rooms_panel:setLabel(app.config.remove_destroyed_rooms and _S.customise_window.option_on or _S.customise_window.option_off)
app:saveConfig()
self:reload()
end
function UICustomise:buttonBack()
self:close()
local window = UIOptions(self.ui, "menu")

View File

@@ -71,23 +71,13 @@ function InstallDirTreeNode:createNewNode(path)
return InstallDirTreeNode(path)
end
--! Test if file name has an .iso extension
local function isIso(name)
if name == nil then
return false
end
local ext = 'iso'
return string.sub(name:lower(), -string.len(ext)) == ext
end
--! Test whether this file node is a directory or iso file.
--
--!return (bool) true if directory or iso, false otherwise
function InstallDirTreeNode:isValidFile(name)
-- Check parent criteria and that it's a directory.
if FileTreeNode.isValidFile(self, name) then
return DirTreeNode.isValidFile(self, name) or isIso(name)
return DirTreeNode.isValidFile(self, name) or FileSystem:isIso(name)
end
return false
end
@@ -112,28 +102,12 @@ function InstallDirTreeNode:getHighlightColour(canvas)
-- Assume root-level things are not TH directories, unless we've already
-- got a list of their children.
highlight_colour = nil
elseif self:getChildCount() >= 3 then
local ngot = 0
local things_to_check = {"data", "levels", "qdata"}
for _, thing in ipairs(things_to_check) do
if not self.children[thing:lower()] then
break
else
ngot = ngot + 1
end
end
if ngot == 3 then
highlight_colour = canvas:mapRGB(0, 255, 0)
self.is_valid_directory = true
end
elseif isIso(self.path) then
local file = io.open(self.path, "rb")
if not file then return nil end
if iso_fs:setRoot(file) then
highlight_colour = canvas:mapRGB(0, 255, 0)
self.is_valid_directory = true
end
io.close(file)
elseif self:getChildCount() >= 3 and TheApp:isThemeHospitalPath(self.path) then
highlight_colour = canvas:mapRGB(0, 255, 0)
self.is_valid_directory = true
elseif FileSystem:isIso(self.path) and iso_fs:setRoot(self.path) then
highlight_colour = canvas:mapRGB(0, 255, 0)
self.is_valid_directory = true
end
self.highlight_colour = highlight_colour
end

View File

@@ -29,7 +29,7 @@ local UIDropdown = _G["UIDropdown"]
--!param parent_window (Window) The window that this dropdown will be attached to
--!param parent_button (Button) The button in the parent_window that this dropdown will be positioned under
--!param items (table) A list of items for the list to display, where each item is a table with at least
-- the field text, and optionally fields font and/or tooltip
-- the field text, and optionally fields font and/or tooltip, which is a table containing text, x and y positions.
--!param callback (function) A function to be called when an item is selected. It is called with two parameters:
-- The parent window and the index of the selected item
--!param colour (table) A colour in the form of {red = ..., green = ..., blue = ...}. Optional if parent_window is a UIResizable
@@ -57,10 +57,16 @@ function UIDropdown:UIDropdown(ui, parent_window, parent_button, items, callback
local y = 0
for i, item in ipairs(items) do
if item.tooltip and item.tooltip[1] then
self:addBevelPanel(1, y + 1, width - 2, height - 2, parent_window.colour):setLabel(item.text, item.font)
:makeButton(-1, -1, width, height, nil, --[[persistable:dropdown_tooltip_callback]] function() self:selectItem(i) end)
:setTooltip(item.tooltip[1], item.tooltip[2] or math.floor(self.ui.app.config.width / 2 - 25),
math.floor(self.ui.app.config.height / 4 - 130 + item.tooltip[3]) or 0)
-- Magic numbers used to find a static position across different screen resolutions.
else
self:addBevelPanel(1, y + 1, width - 2, height - 2, parent_window.colour):setLabel(item.text, item.font)
:makeButton(-1, -1, width, height, nil, --[[persistable:dropdown_callback]] function() self:selectItem(i) end)
-- TODO: tooltips for dropdown items currently deactivated because alignment and conditions for displaying are off for tooltips on sub-windows
--:setTooltip(item.tooltip)
end
y = y + height
end

View File

@@ -88,12 +88,16 @@ function FilteredTreeControl:FilteredTreeControl(root, x, y, width, height, col_
self.num_rows = (self.tree_rect.h - self.y_offset) / self.row_height
-- Magic numbers used to find a static position across different screen resolutions.
local button1x = math.floor(TheApp.ui.app.config.width / 2 - 90)
local button2x = button1x + 210
local buttony = math.floor(TheApp.ui.app.config.height / 4 - 95)
-- Add the two column headers and make buttons on them.
if show_dates then
self:addBevelPanel(1, 1, width - 170, 13, col_bg):setLabel(_S.menu_list_window.name)
:makeButton(0, 0, width - 170, 13, nil, self.sortByName):setTooltip(_S.tooltip.menu_list_window.name)
:makeButton(0, 0, width - 170, 13, nil, self.sortByName):setTooltip(_S.tooltip.menu_list_window.name, button1x, buttony)
self:addBevelPanel(width - 169, 1, 150, 13, col_bg):setLabel(_S.menu_list_window.save_date)
:makeButton(0, 0, 150, 13, nil, self.sortByDate):setTooltip(_S.tooltip.menu_list_window.save_date)
:makeButton(0, 0, 150, 13, nil, self.sortByDate):setTooltip(_S.tooltip.menu_list_window.save_date, button2x, buttony)
end
self.show_dates = show_dates
end

View File

@@ -41,12 +41,18 @@ function UILoadGame:choiceMade(name)
local status, err = pcall(app.load, app, name)
if not status then
err = _S.errors.load_prefix .. err
print(err)
app:loadMainMenu()
app.ui:addWindow(UIInformation(self.ui, {err}))
self:loadError(err)
end
end
--! Output the error when trying to load a game
--!param err The error given
function UILoadGame:loadError(err)
print(err)
TheApp:loadMainMenu()
TheApp.ui:addWindow(UIInformation(TheApp.ui, {err}))
end
function UILoadGame:close()
UIResizable.close(self)
if self.mode == "menu" then

View File

@@ -122,6 +122,7 @@ function UILuaConsole:buttonExecute()
print("Error while executing UserFunction:")
print(err)
end
_ = nil -- Clean up the global after use or it'll corrupt saves
end
function UILuaConsole:buttonClose()

View File

@@ -79,14 +79,17 @@ function UINewGame:UINewGame(ui)
local avail_diff = {
{text = _S.new_game_window.medium, tooltip = _S.tooltip.new_game_window.medium, param = "full"},
{text = _S.new_game_window.medium, tooltip = { _S.tooltip.new_game_window.medium,
nil, 130 }, param = "full"}
}
if TheApp.fs:fileExists("Levels", "Easy01.SAM") then
table.insert(avail_diff, 1, {text = _S.new_game_window.easy, tooltip = _S.tooltip.new_game_window.easy, param = "easy"})
table.insert(avail_diff, 1, {text = _S.new_game_window.easy, tooltip = { _S.tooltip.new_game_window.easy,
nil, 100 }, param = "easy"})
self.difficulty = 2
end
if TheApp.fs:fileExists("Levels", "Hard01.SAM") then
avail_diff[#avail_diff + 1] = {text = _S.new_game_window.hard, tooltip = _S.tooltip.new_game_window.hard, param = "hard"}
avail_diff[#avail_diff + 1] = {text = _S.new_game_window.hard, tooltip = { _S.tooltip.new_game_window.hard,
nil, 175 }, param = "hard"}
end
self.available_difficulties = avail_diff
@@ -194,6 +197,8 @@ end
function UINewGame:startGame(difficulty)
self.ui.app:loadLevel(1, difficulty)
self.ui.app.moviePlayer:playAdvanceMovie(1)
-- Initiate campaign progression. The UI above may now have changed.
if not TheApp.using_demo_files then
TheApp.world.campaign_info = "TH.campaign"

View File

@@ -24,6 +24,10 @@ class "UIOptions" (UIResizable)
---@type UIOptions
local UIOptions = _G["UIOptions"]
-- Constants for most button's width and height
local BTN_WIDTH = 135
local BTN_HEIGHT = 20
local col_bg = {
red = 154,
green = 146,
@@ -89,10 +93,6 @@ function UIOptions:UIOptions(ui, mode)
-- Tracks the current position of the object
self._current_option_index = 1
-- Constants for most button's width and height
local BTN_WIDTH = 135
local BTN_HEIGHT = 20
self:checkForAvailableLanguages()
-- Set up list of resolutions
@@ -166,13 +166,15 @@ function UIOptions:UIOptions(ui, mode)
self.language_button = self.language_panel:makeToggleButton(0, 0, BTN_WIDTH, BTN_HEIGHT, nil, self.dropdownLanguage):setTooltip(_S.tooltip.options_window.select_language)
-- add the Audio global switch.
local audio_status = app:isAudioEnabled()
local audio_y_pos = self:_getOptionYPos()
self:addBevelPanel(20, audio_y_pos, BTN_WIDTH, BTN_HEIGHT, col_shadow, col_bg, col_bg)
:setLabel(_S.options_window.audio):setTooltip(_S.tooltip.options_window.audio_button).lowered = true
self.volume_panel =
self:addBevelPanel(165, audio_y_pos, BTN_WIDTH, BTN_HEIGHT, col_bg):setLabel(app.config.audio and _S.customise_window.option_on or _S.customise_window.option_off)
self:addBevelPanel(165, audio_y_pos, BTN_WIDTH, BTN_HEIGHT, col_bg):setLabel(app.config.audio and audio_status and _S.customise_window.option_on or _S.customise_window.option_off)
self.volume_button = self.volume_panel:makeToggleButton(0, 0, BTN_WIDTH, BTN_HEIGHT, nil, self.buttonAudioGlobal)
:setToggleState(app.config.audio):setTooltip(_S.tooltip.options_window.audio_toggle)
:setToggleState(app.config.audio and audio_status):setTooltip(_S.tooltip.options_window.audio_toggle)
self.volume_button.enabled = audio_status
-- Set scroll speed.
local scroll_y_pos = self:_getOptionYPos()
@@ -221,12 +223,15 @@ local --[[persistable:options_height_textbox_reset]] function height_textbox_res
function UIOptions:checkForAvailableLanguages()
local app = self.app
-- Set up list of available languages
local langs = {}
for _, lang in ipairs(app.strings.languages) do
local langs, c = {}, 1
for _, lang in pairs(app.strings.languages) do
local font = app.strings:getFont(lang)
if app.gfx:hasLanguageFont(font) then
if app.gfx:hasLanguageFont(font) and app.strings.languages_english[lang] then
local eng_name = app.strings.languages_english[lang]
c = c + 1
font = font and app.gfx:loadLanguageFont(font, app.gfx:loadSpriteTable("QData", "Font01V"))
langs[#langs + 1] = {text = lang, font = font, tooltip = _S.tooltip.options_window.language_dropdown_item:format(lang)}
langs[#langs + 1] = { text = lang, font = font,
tooltip = { _S.tooltip.options_window.language_dropdown_item:format(eng_name), nil, BTN_HEIGHT * c } }
end
end
self.available_languages = langs
@@ -247,8 +252,9 @@ function UIOptions:dropdownLanguage(activate)
end
function UIOptions:selectLanguage(number)
local lang = self.app.strings.languages_english[self.available_languages[number]["text"]]
local app = self.ui.app
app.config.language = (self.available_languages[number].text)
app.config.language = (lang)
app:initLanguage()
app:saveConfig()
end

View File

@@ -69,14 +69,6 @@ function UIUpdate:UIUpdate(ui, this_version, new_version, brief_description, dow
self.white_font = app.gfx:loadFont("QData", "Font01V")
self.download_url = download_url
local pathsep = package.config:sub(1, 1)
if pathsep == "\\" then
self.os_is_windows = true
else
self.os_is_windows = false
end
self:addBevelPanel(20, 50, 140, 20, col_shadow, col_bg, col_bg)
:setLabel(_S.update_window.current_version).lowered = true
self:addBevelPanel(20, 70, 140, 20, col_shadow, col_bg, col_bg)
@@ -109,8 +101,10 @@ end
function UIUpdate:buttonDownload()
if self.os_is_windows then
if self.app.os == "windows" then
os.execute("start " .. self.download_url)
elseif self.app.os == "macos" then
os.execute("open " .. self.download_url)
else
os.execute("xdg-open " .. self.download_url)
end

View File

@@ -255,11 +255,6 @@ function UIStaff:onMouseDown(button, x, y)
return Window.onMouseDown(self, button, x, y)
end
-- Helper function to facilitate humanoid_class comparison wrt. Surgeons
local function surg_compat(class)
return class == "Surgeon" and "Doctor" or class
end
function UIStaff:onMouseUp(button, x, y)
local ui = self.ui
if button == "left" then
@@ -271,7 +266,7 @@ function UIStaff:onMouseUp(button, x, y)
-- Right click goes to the next staff member of the same category (NB: Surgeon in same Category as Doctor)
local staff_index = nil
for i, staff in ipairs(ui.hospital.staff) do
if staff_index and surg_compat(staff.humanoid_class) == surg_compat(self.staff.humanoid_class) then
if staff_index and staff.profile.humanoid_class == self.staff.profile.humanoid_class then
ui:addWindow(UIStaff(ui, staff))
return false
end
@@ -282,7 +277,7 @@ function UIStaff:onMouseUp(button, x, y)
-- Try again from beginning of list until staff_index
for i = 1, staff_index - 1 do
local staff = ui.hospital.staff[i]
if surg_compat(staff.humanoid_class) == surg_compat(self.staff.humanoid_class) then
if staff.profile.humanoid_class == self.staff.profile.humanoid_class then
ui:addWindow(UIStaff(ui, staff))
return false
end
@@ -327,6 +322,12 @@ function UIStaff:changeHandymanAttributes(increased)
return
end
-- Show a helpful message if this dialog hasn't been opened yet
if not self.ui.hospital.handyman_popup then
self.ui.adviser:say(_A.information.handyman_adjust)
self.ui.hospital.handyman_popup = true
end
local incr_value = 0.1 -- Increase of 'increased'
local smallest_decr = 0.05 -- Smallest decrement that can be performed.
local decr_attrs = {}

View File

@@ -102,6 +102,11 @@ function UIStaffRise:UIStaffRise(ui, staff, rise_amount)
end
end
-- Staff raise requests pause game
function UIStaffRise:mustPause()
return true
end
function UIStaffRise:getStaffPosition(dx, dy)
local staff = self.staff
local x, y = self.ui.app.map:WorldToScreen(staff.tile_x, staff.tile_y)
@@ -172,10 +177,6 @@ function UIStaffRise:fireStaff()
self.staff.message_callback = nil
self.staff:fire()
self:close()
local world = self.ui.app.world
if world and world:isCurrentSpeed("Pause") then
world:setSpeed(world.prev_speed)
end
end
function UIStaffRise:increaseSalary()
@@ -183,10 +184,6 @@ function UIStaffRise:increaseSalary()
self.staff:increaseWage(self.rise_amount)
self.staff.quitting_in = nil
self:close()
local world = self.ui.app.world
if world and world:isCurrentSpeed("Pause") then
world:setSpeed(world.prev_speed)
end
end
function UIStaffRise:afterLoad(old, new)

View File

@@ -25,6 +25,10 @@ class "UIWatch" (Window)
---@type UIWatch
local UIWatch = _G["UIWatch"]
local TICK_DAYS = 100
local TICK_DAYS_EMERGENCY = 52
local TIMER_SEGMENTS = 13
--!param count_type (string) One of: "open_countdown" or "emergency" or "epidemic"
function UIWatch:UIWatch(ui, count_type)
self:Window()
@@ -33,8 +37,13 @@ function UIWatch:UIWatch(ui, count_type)
self.esc_closes = false
self.modal_class = "open_countdown"
self.tick_rate = math.floor((100 * Date.hoursPerDay()) / 13)
self.tick_timer = self.tick_rate -- Initialize tick timer
if count_type == "emergency" then
self.tick_rate = math.floor((TICK_DAYS_EMERGENCY * Date.hoursPerDay()) / TIMER_SEGMENTS)
self.tick_timer = self.tick_rate
else
self.tick_rate = math.floor((TICK_DAYS * Date.hoursPerDay()) / TIMER_SEGMENTS)
self.tick_timer = self.tick_rate -- Initialize tick timer
end
self.open_timer = 12
self.ui = ui
self.hospital = ui.hospital
@@ -93,7 +102,6 @@ function UIWatch:onCountdownEnd()
end
elseif self.count_type == "initial_opening" then
self.ui.hospital.opened = true
self.ui.hospital.boiler_can_break = true -- boiler can't break whilst build timer is open
self.ui:playSound("fanfare.wav")
end
end

View File

@@ -251,7 +251,7 @@ moods("cold", 3994, 0, true) -- These have no priority sin
moods("hot", 3988, 0, true) -- they will be shown when hovering
moods("queue", 4568, 70) -- no matter what other priorities.
moods("poo", 3996, 5)
moods("sad_money", 4018, 50)
moods("sad_money", 4018, 55)
moods("patient_wait", 5006, 40)
moods("epidemy1", 4566, 55)
moods("epidemy2", 4570, 55)
@@ -492,8 +492,6 @@ local function Humanoid_startAction(self)
elseif class.is(self, Staff) then
ui:addWindow(UIStaff(ui, self))
end
-- Pause the game.
self.world:setSpeed("Pause")
-- Tell the player what just happened.
self.world:gameLog("")
@@ -931,13 +929,14 @@ function Humanoid:tostring()
local result = string.format("%s - class: %s", full_name, class)
result = result .. string.format("\nWarmth: %.3f Happiness: %.3f Fatigue: %.3f Thirst: %.3f Toilet_Need: %.3f Health: %.3f",
result = result .. string.format("\nWarmth: %.3f Happiness: %.3f Fatigue: %.3f Thirst: %.3f Toilet_Need: %.3f Health: %.3f Service Quality: %.3f",
self.attributes["warmth"] or 0,
self.attributes["happiness"] or 0,
self.attributes["fatigue"] or 0,
self.attributes["thirst"] or 0,
self.attributes["toilet_need"] or 0,
self.attributes["health"] or 0)
self.attributes["health"] or 0,
self.attributes["fatigue"] and self:getServiceQuality() or 0)
result = result .. "\nActions: ["
for i = 1, #self.action_queue do

View File

@@ -301,8 +301,16 @@ function Patient:isTreatmentEffective()
local cure_chance = self.hospital.disease_casebook[self.disease.id].cure_effectiveness
cure_chance = cure_chance * self.diagnosis_progress
local die = self.die_anims and math.random(1, 100) > cure_chance
return not die
-- Service quality has a factor on cure chance
local room = self:getRoom()
local min_impact = 20
local service_base = math.max(100 - cure_chance, min_impact)
local scale = 0.2 -- Quality scaled to +-10%
local service_factor = (room:getStaffServiceQuality() - 0.5) * scale
cure_chance = cure_chance + (service_base * service_factor)
return (cure_chance >= math.random(1,100))
end
--! Change patient internal state to "cured".
@@ -312,21 +320,11 @@ function Patient:cure()
self.attributes["health"] = 1
end
--! Patient died, process the event.
function Patient:die()
-- It may happen that this patient was just cured and then the room blew up.
local hospital = self.hospital
if hospital.num_deaths < 1 then
self.world.ui.adviser:say(_A.information.first_death)
end
hospital:humanoidDeath(self)
if not self.is_debug then
local casebook = hospital.disease_casebook[self.disease.id]
casebook.fatalities = casebook.fatalities + 1
end
hospital:msgKilled()
self.hospital:humanoidDeath(self)
self:setMood("dead", "activate")
self.world.ui:playSound("boo.wav") -- this sound is always heard
-- Remove any messages and/or callbacks related to the patient.
self:unregisterCallbacks()
@@ -337,9 +335,6 @@ function Patient:die()
else
self:setNextAction(MeanderAction():setCount(1))
end
if self.is_emergency then
hospital.emergency.killed_emergency_patients = hospital.emergency.killed_emergency_patients + 1
end
self:queueAction(DieAction())
self:updateDynamicInfo(_S.dynamic_info.patient.actions.dying)
end
@@ -472,14 +467,12 @@ function Patient:goHome(reason, disease_id)
if reason == "cured" then
self:setMood("cured", "activate")
self:changeAttribute("happiness", 0.8)
self.world.ui:playSound("cheer.wav") -- This sound is always heard
self.hospital:updateCuredCounts(self)
hosp:updateCuredCounts(self)
self:updateDynamicInfo(_S.dynamic_info.patient.actions.cured)
self.hospital:msgCured()
elseif reason == "kicked" then
self:setMood("exit", "activate")
self.hospital:updateNotCuredCounts(self, reason)
hosp:updateNotCuredCounts(self, reason)
elseif reason == "over_priced" then
self:setMood("sad_money", "activate")
@@ -487,13 +480,13 @@ function Patient:goHome(reason, disease_id)
local treatment_name = self.hospital.disease_casebook[disease_id].disease.name
self.world.ui.adviser:say(_A.warnings.patient_not_paying:format(treatment_name))
self.hospital:updateNotCuredCounts(self, reason)
hosp:updateNotCuredCounts(self, reason)
self:clearDynamicInfo()
self:updateDynamicInfo(_S.dynamic_info.patient.actions.prices_too_high)
self:setDynamicInfo('text', {"", _S.dynamic_info.patient.actions.prices_too_high})
elseif reason == "evacuated" then
self:clearDynamicInfo()
self:setDynamicInfo('text', {_S.dynamic_info.patient.actions.epidemic_sent_home})
self:setDynamicInfo('text', {"", _S.dynamic_info.patient.actions.epidemic_sent_home})
self:setMood("exit","activate")
else
@@ -516,6 +509,11 @@ function Patient:goHome(reason, disease_id)
self.world.dispatcher:dropFromQueue(self)
end
-- allow timer to end early and after going_home is set
if self.is_emergency then
hosp:checkEmergencyOver()
end
local room = self:getRoom()
if room then
room:makeHumanoidLeave(self)
@@ -691,13 +689,8 @@ function Patient:tickDay()
-- It is nice to see plants, but dead plants make you unhappy
self.world:findObjectNear(self, "plant", 2, function(x, y)
local plant = self.world:getObject(x, y, "plant")
if not plant then
return
end
if plant:isPleasing() then
self:changeAttribute("happiness", 0.0002)
else
self:changeAttribute("happiness", -0.0002)
if plant then
self:changeAttribute("happiness", -0.0003 + (plant:isPleasingFactor() * 0.0001))
end
end)
-- It always makes you happy to see you are in safe place
@@ -883,7 +876,7 @@ function Patient:setTile(x, y)
self.hospital.hospital_littered = true
-- A callout is only needed if there are no handymen employed
if not self.hospital:hasStaffOfCategory("Handyman") then
if self.hospital:countStaffOfCategory("Handyman", 1) == 0 then
self.world.ui.adviser:say(_A.staff_advice.need_handyman_litter)
end
end
@@ -951,7 +944,11 @@ function Patient:updateDynamicInfo(action_string)
-- The cure was guessed
info = _S.dynamic_info.patient.guessed_diagnosis:format(self.disease.name)
else
info = _S.dynamic_info.patient.diagnosed:format(self.disease.name)
if self.is_emergency then
info = _S.dynamic_info.patient.emergency:format(self.disease.name)
else
info = _S.dynamic_info.patient.diagnosed:format(self.disease.name)
end
end
self:setDynamicInfo('progress', nil)
else
@@ -975,6 +972,8 @@ function Patient:updateDynamicInfo(action_string)
elseif self.vaccinated then
self:setDynamicInfo('text',
{action_string, _S.dynamic_info.patient.actions.epidemic_vaccinated, info})
else
self:setDynamicInfo('text', {action_string, "", info})
end
else
self:setDynamicInfo('text', {action_string, "", info})
@@ -999,8 +998,10 @@ function Patient:updateMessage(choice)
-- enable only if research department is built and a room in the treatment chain is undiscovered
local req = self.hospital:checkDiseaseRequirements(self.disease.id)
if req then
enabled = (self.hospital:countRoomOfType("research", 1) > 0 and
self.hospital:countStaffOfCategory("Researcher", 1) > 0)
local strings = _S.fax.disease_discovered_patient_choice
enabled = self.hospital:hasRoomOfType("research") and self.hospital:hasStaffOfCategory("Researcher")
local output_text = strings.can_not_cure
if #req.rooms == 1 then
local room_name, required_staff, staff_name = self.world:getRoomNameAndRequiredStaffName(req.rooms[1])

View File

@@ -62,11 +62,7 @@ function Staff:tickDay()
self.world:findObjectNear(self, "plant", 2, function(x, y)
local plant = self.world:getObject(x, y, "plant")
if plant then
if plant:isPleasing() then
self:changeAttribute("happiness", 0.002)
else
self:changeAttribute("happiness", -0.003)
end
self:changeAttribute("happiness", -0.003 + (plant:isPleasingFactor() * 0.001))
end
end)
-- It always makes you happy to see you are in safe place
@@ -97,8 +93,23 @@ function Staff:tickDay()
self:changeAttribute("happiness", 0.05)
end
--TODO windows in your work space and a large space to work in add to happiness
-- working in a small space makes you unhappy
local room = self:getRoom()
if room then
-- It always makes you happy to see the outdoors (or windows to anywhere)
local count = room:countWindows()
if room.room_info.id == "staff_room" then -- Pleased another bit
count = count * 2
end
if count > 0 then
-- More windows help but in smaller increments
self:changeAttribute("happiness", math.round(math.log(count)) / 1000)
end
-- Extra space in the room you are in adds to your happiness
local extraspace = (room.width * room.height) / (room.room_info.minimum_size * room.room_info.minimum_size)
-- Greater space helps but in smaller increments
self:changeAttribute("happiness", math.round(math.log(extraspace)) / 1000)
end
end
function Staff:tick()
@@ -441,7 +452,7 @@ function Staff:setHospital(hospital)
end
-- Helper function to decide if Staff fulfills a criterion
-- (one of "Doctor", "Nurse", "Psychiatrist", "Surgeon", "Researcher" and "Handyman")
-- (one of "Doctor", "Nurse", "Psychiatrist", "Surgeon", "Researcher" and "Handyman", "Receptionist", "Junior", "Consultant")
function Staff:fulfillsCriterion(criterion)
return false
end
@@ -659,19 +670,20 @@ function Staff:getDrawingLayer()
return 4
end
--! Estimate staff service quality based on skills, fatigue and happiness.
--! Estimate staff service quality based on skills, restfulness (inverse of fatigue) and happiness.
--!return (float) between [0-1] indicating quality of the service.
function Staff:getServiceQuality()
-- weights
local skill_weight = 0.7
local fatigue_weight = 0.2
local restfulness_weight = 0.2
local happiness_weight = 0.1
local weighted_skill = skill_weight * self.profile.skill
local weighted_fatigue = fatigue_weight * self.attributes["fatigue"]
-- Less fatigue is better
local weighted_restfulness = restfulness_weight * (1 - self.attributes["fatigue"])
local weighted_happiness = happiness_weight * self.attributes["happiness"]
return weighted_skill + weighted_fatigue + weighted_happiness
return weighted_skill + weighted_restfulness + weighted_happiness
end
--[[ Return string representation

View File

@@ -249,17 +249,18 @@ local profile_attributes = {
Psychiatrist = "is_psychiatrist",
Surgeon = "is_surgeon",
Researcher = "is_researcher",
Junior = "is_junior",
Consultant = "is_consultant",
}
-- Helper function to decide if Staff fulfills a criterion
-- (one of "Doctor", "Nurse", "Psychiatrist", "Surgeon", "Researcher" and "Handyman")
-- (one of "Doctor", "Nurse", "Psychiatrist", "Surgeon", "Researcher" and "Handyman", "Receptionist", "Junior", "Consultant")
function Doctor:fulfillsCriterion(criterion)
if criterion == "Doctor" then
return true
elseif criterion == "Psychiatrist" or criterion == "Surgeon" or criterion == "Researcher" then
if self.profile and self.profile[profile_attributes[criterion]] == 1.0 then
return true
end
end
if self.profile and self.profile[profile_attributes[criterion]] == 1.0 then
return true
end
return false
end

View File

@@ -84,12 +84,9 @@ function Handyman:onPlaceInCorridor()
end
-- Helper function to decide if Handyman fulfills a criterion
-- (one of "Doctor", "Nurse", "Psychiatrist", "Surgeon", "Researcher" and "Handyman")
-- (one of "Doctor", "Nurse", "Psychiatrist", "Surgeon", "Researcher" and "Handyman", "Receptionist", "Junior", "Consultant")
function Handyman:fulfillsCriterion(criterion)
if criterion == "Handyman" then
return true
end
return false
return criterion == "Handyman"
end
function Handyman:afterLoad(old, new)

View File

@@ -42,12 +42,9 @@ function Nurse:leaveAnnounce()
end
-- Helper function to decide if Staff fulfills a criterion
-- (one of "Doctor", "Nurse", "Psychiatrist", "Surgeon", "Researcher" and "Handyman")
-- (one of "Doctor", "Nurse", "Psychiatrist", "Surgeon", "Researcher" and "Handyman", "Receptionist", "Junior", "Consultant")
function Nurse:fulfillsCriterion(criterion)
if criterion == "Nurse" then
return true
end
return false
return criterion == "Nurse"
end
function Nurse:adviseWrongPersonForThisRoom()

View File

@@ -40,10 +40,8 @@ function Receptionist:tickDay()
end
function Receptionist:leaveAnnounce()
local announcement_priority = AnnouncementPriority.High
local receptionist_leave_sounds = {"sack007.wav", "sack008.wav",}
self.world.ui:playAnnouncement(receptionist_leave_sounds[math.random(1, #receptionist_leave_sounds)], announcement_priority)
self.world.ui:playAnnouncement(receptionist_leave_sounds[math.random(1, #receptionist_leave_sounds)], AnnouncementPriority.Critical) -- must always be played even without receptionist
end
function Receptionist:isTiring()
@@ -61,7 +59,7 @@ end
function Receptionist:needsWorkStation()
if self.hospital and not self.hospital.receptionist_msg then
if self.world.object_counts["reception_desk"] == 0 then
if self.hospital:countReceptionDesks() == 0 then
self.world.ui.adviser:say(_A.warnings.no_desk_4)
self.hospital.receptionist_msg = true
end
@@ -85,9 +83,9 @@ end
-- Helper function to decide if Staff fulfills a criterion
-- (one of "Doctor", "Nurse", "Psychiatrist", "Surgeon", "Researcher" and "Handyman")
function Receptionist:fulfillsCriterion(criterion) -- luacheck: no unused args
return false
-- (one of "Doctor", "Nurse", "Psychiatrist", "Surgeon", "Researcher" and "Handyman", "Receptionist", "Junior", "Consultant")
function Receptionist:fulfillsCriterion(criterion)
return criterion == "Receptionist"
end
function Receptionist:getDrawingLayer()

View File

@@ -1,4 +1,5 @@
--[[ Copyright (c) 2011 John Pirie
Copyright (c) 2020 lewri
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
@@ -18,7 +19,42 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. --]]
--[[
---------------------------------- VIP Rating System --------------------------------
Vip rating is calculated between 0 and 15
If rating exceeds values, it will be capped as necessary
1. LITTER OBJECTS
- a. General litter and vomit 'litter' is counted
- b. At end of visit, if number <=10 award -1
- c. Otherwise, award +1
2. STAFF TIREDNESS
- a. If staff <=1, award +4 and skip other checks
- b. If any staff member very tired (over 0.7), award 2
- c. If average staff tiredness over tired (0.5), award 1
- d. Else award -1
3. PATIENTS
- a. If average patient health >= 0.2, award -1
- b. If average patient health < 0.2, award +1
- c. Assess patient warmth, too hot/cold award +2, perfect -1
- d. Average happiness, award 3 if <0.2; 2 if <0.4, 1 if <0.6, 0 if <0.8, else -1
- e. Check if anyone has died during visit, punish based on severity
- f. Check how many patients are cured vs. all patients, award based on %
- g. Check seating. Award -1 for more seated than standing, else +1
- h. Check maximum queue size. Award based on the longest queue
4. DOCTORS
- a. If no doctors, award +4 and skip other checks
- b. If more than half doctors are consultants, award -2
- c. If more than half doctors are juniors, award +2
5. ROOMS
- a. If there are no active rooms, award +4
- b. If rooms not crashed (exploded) <3, award +1
- c. Get average room evaluation score, award based on average level
-------------------------------------------------------------------------------------
--]]
--[[ initialisation --]]
corsixth.require("announcer")
corsixth.require("utility")
local AnnouncementPriority = _G["AnnouncementPriority"]
@@ -34,25 +70,29 @@ function Vip:Vip(...)
self.action_string = ""
self.name=""
self.announced = false
self.vip_rating = 50
--First we should generate an initial VIP rating
self.vip_rating = 12 - math.random(0,5)
self.cash_reward = 0
self.rep_reward = 0
self.vip_message = 0
self.enter_deaths = 0
self.enter_visitors = 0
self.enter_explosions = 0
-- patients in the hospital when VIP arrives
self.enter_patients = 0
self.enter_cures = 0
self.num_vomit_noninducing = 0
self.num_vomit_inducing = 0
self.found_vomit = {}
self.num_visited_rooms = 0
self.room_eval = 0
-- sets the chance VIP visits each room, default is 50% or 1/2. For every 40 rooms in a hospital over 79 we increase n by 1 and chance is 1/n+1
self.room_visit_chance = 1
self.waiting = 0
end
-- Check if it is cold or hot around the vip and increase/decrease the
-- feeling of warmth accordingly. Returns whether the calling function should proceed.
--[[--VIP while on premesis--]]
function Vip:tickDay()
-- for the vip
if self.waiting then
@@ -62,48 +102,63 @@ function Vip:tickDay()
-- No rooms have been built yet
self:goHome()
end
-- First let the previous room go.
-- Include this when the VIP is supposed to block doors again.
--[[if self.next_room then
self.next_room.door.reserved_for = nil
self.next_room:tryAdvanceQueue()
end--]]
-- Find out which next room to visit.
self.next_room_no, self.next_room = next(self.world.rooms, self.next_room_no)
-- Make sure that this room is active
while self.next_room and not self.next_room.is_active do
self.next_room_no, self.next_room = next(self.world.rooms, self.next_room_no)
end
self:setNextAction(VipGoToNextRoomAction())
self:getNextRoom()
self.waiting = nil
end
end
if not self.going_home then
self.world:findObjectNear(self, "litter", 8, function(x, y)
local litter = self.world:getObject(x, y, "litter")
if not litter then
return
end
self.world:findObjectNear(self, "litter", 8, function(x, y)
local litter = self.world:getObject(x, y, "litter")
if not litter then
return
end
local alreadyFound = false
for i=1, (self.num_vomit_noninducing + self.num_vomit_inducing) do
if self.found_vomit[i] == litter then
alreadyFound = true
break
end
end
local alreadyFound = false
for i=1, (self.num_vomit_noninducing + self.num_vomit_inducing) do
if self.found_vomit[i] == litter then
alreadyFound = true
self.found_vomit[(self.num_vomit_noninducing + self.num_vomit_inducing + 1)] = litter
if not alreadyFound then
if litter:anyLitter() then
self.num_vomit_noninducing = self.num_vomit_noninducing + 1
else
self.num_vomit_inducing = self.num_vomit_inducing + 1
end
end
end)
end
return Humanoid.tickDay(self)
end
function Vip:getNextRoom()
-- Find out the next room to visit.
self.next_room_no, self.next_room = next(self.world.rooms, self.next_room_no)
if self.next_room == nil then
return self:setNextAction(VipGoToNextRoomAction())
end
-- Always visit the first room.
if self.num_visited_rooms > 0 then
local roll_to_visit = math.random(0, self.room_visit_chance)
-- Run a loop until roll_to_visit passes, or we hit a room the VIP must visit
while roll_to_visit ~= self.room_visit_chance and not self.next_room.room_info.vip_must_visit do
self.next_room_no, self.next_room = next(self.world.rooms, self.next_room_no) -- get new room
if self.next_room == nil then
-- We ran out of rooms, time to leave
break
end
roll_to_visit = math.random(0, self.room_visit_chance)
end
self.found_vomit[(self.num_vomit_noninducing + self.num_vomit_inducing + 1)] = litter
if not alreadyFound then
if litter:anyLitter() then
self.num_vomit_noninducing = self.num_vomit_noninducing + 1
else
self.num_vomit_inducing = self.num_vomit_inducing + 1
end
end
end)
return Humanoid.tickDay(self)
end
-- Make sure room is active. If not, always visit the next available room.
while self.next_room and not self.next_room.is_active do
self.next_room_no, self.next_room = next(self.world.rooms, self.next_room_no)
end
self:setNextAction(VipGoToNextRoomAction())
end
-- display the VIP name in the info box
@@ -111,6 +166,7 @@ function Vip:updateDynamicInfo(action_string)
self:setDynamicInfo('text', {self.name})
end
--[[--VIP is leaving--]]
function Vip:goHome()
if self.going_home then
return
@@ -126,62 +182,85 @@ function Vip:goHome()
self:despawn()
end
-- called when the vip is out of the hospital grounds
function Vip:evaluateRoom()
local room_extinguisher = 0
local room_plant = 0
local room_bin = 0
-- Another room visited.
self.num_visited_rooms = self.num_visited_rooms + 1
local room = self.next_room
-- If the player is about to kill a live patient for research, punish hard
if room.room_info.id == "research" then
if room:getPatient() then
self.vip_rating = self.vip_rating + 6
end
end
-- Evaluate the room we're currently looking at
for object, _ in pairs(room.objects) do
if object.object_type.id == "extinguisher" and room_extinguisher == 0 then
self.room_eval = self.room_eval + 1
-- Only count this object type once
room_extinguisher = 1
elseif object.object_type.id == "plant" then
room_plant = object:isDying() and room_plant - 1 or room_plant + 1
elseif object.object_type.id == "bin" and room_bin == 0 then
self.room_eval = self.room_eval + 1
-- Only count this object type once
room_bin = 1
end
if object.strength then
self.room_eval = object:isBreaking() and self.room_eval - 1 or self.room_eval + 1
end
end
-- Check whether we had more good or bad plants
if room_plant < 0 then
self.room_eval = self.room_eval - 1
elseif room_plant > 0 then
self.room_eval = self.room_eval + 1
end
self:getNextRoom()
end
--[[--VIP has left--]]
-- Called when the vip is out of the hospital grounds
function Vip:onDestroy()
local message
-- First of all there's a special message if we're in free build mode.
if self.world.free_build_mode then
self.last_hospital.reputation = self.last_hospital.reputation + 20
self.last_hospital:unconditionalChangeReputation(20)
message = {
{text = _S.fax.vip_visit_result.vip_remarked_name:format(self.name)},
{text = _S.fax.vip_visit_result.remarks.free_build[math.random(1, 3)]},
choices = {{text = _S.fax.vip_visit_result.close_text, choice = "close"}}
}
elseif self.vip_rating == 1 then
self.last_hospital.reputation = self.last_hospital.reputation - 10
message = {
{text = _S.fax.vip_visit_result.vip_remarked_name:format(self.name)},
{text = _S.fax.vip_visit_result.remarks.very_bad[math.random(1, 3)]},
{text = _S.fax.vip_visit_result.rep_loss},
choices = {{text = _S.fax.vip_visit_result.close_text, choice = "close"}}
}
elseif self.vip_rating == 2 then
self.last_hospital.reputation = self.last_hospital.reputation - 5
message = {
{text = _S.fax.vip_visit_result.vip_remarked_name:format(self.name)},
{text = _S.fax.vip_visit_result.remarks.bad[math.random(1, 3)]},
{text = _S.fax.vip_visit_result.rep_loss},
choices = {{text = _S.fax.vip_visit_result.close_text, choice = "close"}}
}
elseif self.vip_rating == 3 then
message = {
{text = _S.fax.vip_visit_result.vip_remarked_name:format(self.name)},
{text = _S.fax.vip_visit_result.remarks.mediocre[math.random(1, 3)]},
choices = {{text = _S.fax.vip_visit_result.close_text, choice = "close"}}
}
elseif self.vip_rating == 4 then
elseif self.vip_rating <= 7 then
self.last_hospital:receiveMoney(self.cash_reward, _S.transactions.vip_award)
self.last_hospital.reputation = self.last_hospital.reputation + (math.round(self.cash_reward / 100))
self.last_hospital:unconditionalChangeReputation(self.rep_reward)
self.last_hospital.pleased_vips_ty = self.last_hospital.pleased_vips_ty + 1
message = {
{text = _S.fax.vip_visit_result.vip_remarked_name:format(self.name)},
{text = _S.fax.vip_visit_result.remarks.good[math.random(1, 3)]},
{text = _S.fax.vip_visit_result.ordered_remarks[self.vip_message]},
{text = _S.fax.vip_visit_result.rep_boost},
{text = _S.fax.vip_visit_result.cash_grant:format(self.cash_reward)},
choices = {{text = _S.fax.vip_visit_result.close_text, choice = "close"}}
}
elseif self.vip_rating >=8 and self.vip_rating < 11 then
-- Dont tell player about any rep change in this range
self.last_hospital:unconditionalChangeReputation(self.rep_reward)
message = {
{text = _S.fax.vip_visit_result.vip_remarked_name:format(self.name)},
{text = _S.fax.vip_visit_result.ordered_remarks[self.vip_message]},
choices = {{text = _S.fax.vip_visit_result.close_text, choice = "close"}}
}
else
self.last_hospital:receiveMoney(self.cash_reward, _S.transactions.vip_award)
self.last_hospital.reputation = self.last_hospital.reputation + (math.round(self.cash_reward / 100))
self.last_hospital.pleased_vips_ty = self.last_hospital.pleased_vips_ty + 1
if self.vip_rating == 5 then
message = {
{text = _S.fax.vip_visit_result.vip_remarked_name:format(self.name)},
{text = _S.fax.vip_visit_result.remarks.super[math.random(1, 3)]},
{text = _S.fax.vip_visit_result.rep_boost},
{text = _S.fax.vip_visit_result.cash_grant:format(self.cash_reward)},
choices = {{text = _S.fax.vip_visit_result.close_text, choice = "close"}}
}
end
self.last_hospital:unconditionalChangeReputation(self.rep_reward)
message = {
{text = _S.fax.vip_visit_result.vip_remarked_name:format(self.name)},
{text = _S.fax.vip_visit_result.ordered_remarks[self.vip_message]},
{text = _S.fax.vip_visit_result.rep_loss},
choices = {{text = _S.fax.vip_visit_result.close_text, choice = "close"}}
}
end
self.world.ui.bottom_panel:queueMessage("report", message, nil, 24 * 20, 1)
@@ -202,268 +281,216 @@ function Vip:announce()
end
end
function Vip:evaluateRoom()
-- Another room visited.
self.num_visited_rooms = self.num_visited_rooms + 1
local room = self.next_room
-- Consider doing this when reserving the door instead
if room.door.queue then
room.door.queue:unexpect(self)
end
-- if the player is about to kill a live patient for research, lower their rating dramatically
if room.room_info.id == "research" then
if room:getPatient() then
self.vip_rating = self.vip_rating - 80
end
end
if room.staff_member then
if room.staff_member.profile.skill > 0.9 then
self.room_eval = self.room_eval + 3
end
if room.staff_member.attributes["fatigue"] then
if room.staff_member.attributes["fatigue"] < 0.4 then
self.room_eval = self.room_eval + 2
end
end
end
-- evaluate the room we're currently looking at
for object, _ in pairs(room.objects) do
if object.object_type.id == "extinguisher" then
self.room_eval = self.room_eval + 1
break
elseif object.object_type.id == "plant" then
if object.days_left >= 10 then
self.room_eval = self.room_eval + 1
elseif object.days_left <= 3 then
self.room_eval = self.room_eval - 1
end
break
end
if object.strength then
if object.strength > (object.object_type.default_strength / 2) then
self.room_eval = self.room_eval + 1
else
self.room_eval = self.room_eval - 3
end
end
end
end
function Vip:evaluateEmergency(success)
-- Make sure that the VIP is still actually in the process of evaluation
if not self.going_home then
if success then
self.vip_rating = self.vip_rating + 10
else
self.vip_rating = self.vip_rating - 15
end
end
end
function Vip:setVIPRating()
--check the visitor to patient death ratio
local death_diff = self.hospital.num_deaths - self.enter_deaths
local visitors_diff = self.hospital.num_visitors - self.enter_visitors
if death_diff == 0 then
if visitors_diff ~= 0 then --if there have been no new patients, no +/- points
self.vip_rating = self.vip_rating + 20
end
else
local death_ratio = visitors_diff / death_diff
local death_ratio_rangemap = {
{upper = 2, value = -20},
{upper = 4, value = -10},
{upper = 8, value = 0},
{upper = 12, value = 5},
{value = 10}
}
self.vip_rating = self.vip_rating + rangeMapLookup(death_ratio, death_ratio_rangemap)
end
--check the visitor to patient cure ratio
local cure_diff = self.hospital.num_cured - self.enter_cures
if cure_diff == 0 then
if visitors_diff ~= 0 then --if there have been no new patients, no +/- points
self.vip_rating = self.vip_rating - 10
end
else
local cure_ratio = visitors_diff / cure_diff
local cure_ratio_rangemap = {
{upper = 3, value = 20},
{upper = 6, value = 10},
{upper = 10, value = 0},
{upper = 12, value = -5},
{value = -10}
}
self.vip_rating = self.vip_rating + rangeMapLookup(cure_ratio, cure_ratio_rangemap)
end
-- check for the average queue length
local sum_queue = 0
local room_count = 0
-- First do room code for later
local count_rooms = 0
local max_queue = 0
for _, room in pairs(self.world.rooms) do
if room.door.queue then
sum_queue = sum_queue + room.door.queue:size()
end
if not room.crashed then
room_count = room_count + 1
count_rooms = count_rooms + 1
end
if room.door.queue then
max_queue = max_queue < room.door.queue:size() and room.door.queue:size() or max_queue
end
end
if room_count == 0 then
self.vip_rating = self.vip_rating - 100
elseif sum_queue == 0 then
self.vip_rating = self.vip_rating + 6
--[[-- Group factor 1: Litter--]]
if (self.num_vomit_noninducing + self.num_vomit_inducing) <= 10 then
self.vip_rating = self.vip_rating - 1
else
local queue_ratio = sum_queue / room_count
local queue_ratio_rangemap = {
{upper = 2, value = 6},
{upper = 5, value = 3},
{upper = 9, value = 0},
{upper = 11, value = -3},
{value = -6}
}
self.vip_rating = self.vip_rating + rangeMapLookup(queue_ratio, queue_ratio_rangemap)
self.vip_rating = self.vip_rating + 1
end
-- now we check for toilet presence
local sum_toilets = 0
for _, room in pairs(self.world.rooms) do
if room.room_info.id == "toilets" then
for object, _ in pairs(room.objects) do
if object.object_type.id == "loo" then
sum_toilets = sum_toilets + 1
end
--[[-- Group factor 2: Staff tiredness--]]
-- First get staff members
local count_staff = #self.hospital.staff
if count_staff > 1 then
-- Loop through staff tiredness, if any above verytired, break loop
for _, staff in ipairs(self.hospital.staff) do
if staff.attributes["fatigue"] ~= nil and staff.attributes["fatigue"] >= 0.7 then
self.vip_rating = self.vip_rating + 2
break
end
end
end
if sum_toilets == 0 then
self.vip_rating = self.vip_rating - 6
-- Average all staff tiredness
local avg_tired = self.hospital:getAverageStaffAttribute("fatigue", 1)
if avg_tired >= 0.5 then
self.vip_rating = self.vip_rating + 1
else
self.vip_rating = self.vip_rating - 1
end
else
local patients_per_toilet = #self.hospital.patients / sum_toilets
local toilet_ratio_rangemap = {
{upper = 10, value = 6},
{upper = 20, value = 3},
{upper = 40, value = 0},
{value = -3}
}
self.vip_rating = self.vip_rating + rangeMapLookup(patients_per_toilet, toilet_ratio_rangemap)
-- Penalise where there is one or no staff
self.vip_rating = self.vip_rating + 4
end
-- check the levels of non-vomit inducing litter in the hospital
local litter_ratio_rangemap = {
{upper = 3, value = 4},
{upper = 5, value = 2},
{upper = 7, value = 0},
{upper = 8, value = -2},
{value = -4}
}
self.vip_rating = self.vip_rating + rangeMapLookup(self.num_vomit_noninducing, litter_ratio_rangemap)
--[[-- Group factor 3: Patients--]]
-- First check we had patients this visit
local patients_this_visit = self.enter_patients + self.hospital.num_visitors - self.enter_visitors
if patients_this_visit > 0 then
-- Average all patient health
local avg_health = self.hospital:getAveragePatientAttribute("health", 0.19)
if avg_health >= 0.2 then
self.vip_rating = self.vip_rating - 1
else
self.vip_rating = self.vip_rating + 1
end
-- check the levels of vomit inducing litter in the hospital
local inducing_ratio_rangemap = {
{upper = 3, value = 8},
{upper = 5, value = 4},
{upper = 6, value = 0},
{upper = 7, value = -6},
{upper = 10, value = -12},
{upper = 12, value = -16},
{value = -20}
}
self.vip_rating = self.vip_rating + rangeMapLookup(self.num_vomit_inducing, inducing_ratio_rangemap)
-- Get patient warmth
local avg_warmth = self.hospital:getAveragePatientAttribute("warmth", nil)
-- Punish if too cold/hot
if avg_warmth then
local patients_warmth_ratio_rangemap = {
{upper = 0.22, value = 2},
{upper = 0.36, value = -1},
{value = 2}
}
self.vip_rating = self.vip_rating + rangeMapLookup(avg_warmth, patients_warmth_ratio_rangemap)
end
-- if there were explosions, hit the user hard
if self.hospital.num_explosions ~= self.enter_explosions then
self.vip_rating = self.vip_rating - 70
-- Check average patient happiness
local avg_happiness = self.hospital:getAveragePatientAttribute("happiness", nil)
if avg_happiness then
local patients_happy_ratio_rangemap = {
{upper = 0.20, value = 3},
{upper = 0.40, value = 2},
{upper = 0.60, value = 1},
{upper = 0.80, value = 0},
{value = -1}
}
self.vip_rating = self.vip_rating + rangeMapLookup(avg_happiness, patients_happy_ratio_rangemap)
end
-- Check the visitor to patient death ratio
local death_diff = self.hospital.num_deaths - self.enter_deaths
if death_diff ~= 0 then -- no deaths are good, but also expected
local death_ratio = patients_this_visit / death_diff
local death_ratio_rangemap = {
{upper = 2, value = 4},
{upper = 4, value = 3},
{upper = 8, value = 2},
{upper = 12, value = 1},
{value = 0}
}
self.vip_rating = self.vip_rating + rangeMapLookup(death_ratio, death_ratio_rangemap)
end
-- Check the visitor to patient cure ratio
local cure_diff = self.hospital.num_cured - self.enter_cures
if cure_diff ~= 0 then -- no cures are bad
local cure_ratio = patients_this_visit / cure_diff
local cure_ratio_rangemap = {
{upper = 2, value = -1},
{upper = 3, value = 0},
{upper = 4, value = 1},
{upper = 5, value = 2},
{value = 3}
}
self.vip_rating = self.vip_rating + rangeMapLookup(cure_ratio, cure_ratio_rangemap)
else
self.vip_rating = self.vip_rating + 3
end
-- Check the seating : standing ratio of waiting patients
local sum_sitting, sum_standing = self.hospital:countSittingStanding()
if (sum_sitting + sum_standing) ~= 0 then
if sum_sitting >= sum_standing or sum_standing == 0 then
self.vip_rating = self.vip_rating - 1
else
self.vip_rating = self.vip_rating + 1
end
end
-- Check the maximum queue length
if max_queue == 0 then
self.vip_rating = self.vip_rating - 1
else
local queue_ratio_rangemap = {
{upper = 3, value = -1},
{upper = 6, value = 0},
{upper = 9, value = 1},
{value = 2}
}
self.vip_rating = self.vip_rating + rangeMapLookup(max_queue, queue_ratio_rangemap)
end
end
-- check the vip heat level
local heat_ratio_rangemap = {
{upper = 0.20, value = -5},
{upper = 0.40, value = -3},
{upper = 0.60, value = 0},
{upper = 0.80, value = 3},
{value = 5}
}
self.vip_rating = self.vip_rating + rangeMapLookup(self.attributes["warmth"], heat_ratio_rangemap)
-- check the seating : standing ratio of waiting patients
-- find all the patients who are currently waiting around
local sum_sitting, sum_standing = self.hospital:countSittingStanding()
if sum_sitting >= sum_standing then
--[[--Group factor 4: Doctor ratios--]]
-- First get all doctors
local num_docs = self.hospital:countStaffOfCategory("Doctor")
-- No doctors are bad
if num_docs == 0 then
self.vip_rating = self.vip_rating + 4
else
self.vip_rating = self.vip_rating - 4
-- Count num. consultants, num. juniors
local num_cons = self.hospital:countStaffOfCategory("Consultant")
local num_junior = self.hospital:countStaffOfCategory("Junior")
-- Check consultant and junior proportions
if num_cons / num_docs > 0.5 then
self.vip_rating = self.vip_rating - 1
elseif num_junior / num_docs > 0.5 then
self.vip_rating = self.vip_rating + 1
end
end
-- check average patient thirst
local avg_thirst = self.hospital:getAveragePatientAttribute("thirst", nil)
if avg_thirst then
local thirst_ratio_rangemap = {
{upper = 0.20, value = -5},
{upper = 0.40, value = -1},
{upper = 0.60, value = 0},
{upper = 0.80, value = 1},
{value = 3}
}
self.vip_rating = self.vip_rating + rangeMapLookup(avg_thirst, thirst_ratio_rangemap)
end
if self.num_visited_rooms ~= 0 then
self.vip_rating = self.vip_rating + self.room_eval / self.num_visited_rooms
end
-- check average patient happiness
local avg_happiness = self.hospital:getAveragePatientAttribute("happiness", nil)
if avg_happiness then
local patients_happy_ratio_rangemap = {
{upper = 0.20, value = -10},
{upper = 0.40, value = -5},
{upper = 0.60, value = 0},
{upper = 0.80, value = 5},
{value = 10}
}
self.vip_rating = self.vip_rating + rangeMapLookup(avg_happiness, patients_happy_ratio_rangemap)
end
-- check average staff happiness
avg_happiness = self.hospital:getAverageStaffAttribute("happiness", nil)
if avg_happiness then
local staff_happy_ratio_rangemap = {
{upper = 0.20, value = -10},
{upper = 0.40, value = -5},
{upper = 0.60, value = 0},
{upper = 0.80, value = 5},
{value = 10}
}
self.vip_rating = self.vip_rating + rangeMapLookup(avg_happiness, staff_happy_ratio_rangemap)
end
-- set the cash reward value
if tonumber(self.world.map.level_number) then
self.cash_reward = math.round(self.world.map.level_number * self.vip_rating) * 10
--[[--Group factor 5: Rooms--]]
-- Low room numbers incur a penalty
if count_rooms < 1 then
self.vip_rating = self.vip_rating + 4
else
-- custom level, it has no level number. Default back to one.
self.cash_reward = math.round(1 * self.vip_rating) * 10
end
if self.cash_reward > 2000 then
self.cash_reward = 2000
if count_rooms < 3 then
self.vip_rating = self.vip_rating + 1
end
-- Room decor average
local avg_room_eval = self.room_eval / self.num_visited_rooms
local room_eval_rangemap = {
{upper = 1.5, value = 3},
{upper = 2, value = 1},
{upper = 3, value = 0},
{value = -1}
}
if self.num_visited_rooms ~= 0 then
self.vip_rating = self.vip_rating + rangeMapLookup(avg_room_eval, room_eval_rangemap)
end
end
-- give the rating between 1 and 5
local rating_ratio_rangemap = {
{upper = 25, value = 1},
{upper = 45, value = 2},
{upper = 65, value = 3},
{upper = 85, value = 4},
{value = 5}
--[[--Finalise score--]]
-- Documented rewards
local rewards = {
[1] = 4000,
[2] = 2000,
[3] = 1500,
[4] = 1200,
[5] = 800,
[6] = 400,
[7] = 200,
[8] = 0,
}
self.vip_rating = rangeMapLookup(self.vip_rating, rating_ratio_rangemap)
-- Documented reps
local rep_change = {
[1] = 50,
[2] = 45,
[3] = 40,
[4] = 35,
[5] = 30,
[6] = 25,
[7] = 20,
[8] = 15,
[9] = 10,
[10] = 5,
[11] = -5,
[12] = -10,
[13] = -15,
[14] = -20,
[15] = -25,
}
-- Set rewards
self.vip_rating = self.vip_rating > 15 and 15 or self.vip_rating
self.vip_rating = self.vip_rating < 1 and 1 or self.vip_rating
self.cash_reward = rewards[self.vip_rating] or 0
self.rep_reward = rep_change[self.vip_rating]
self.vip_message = self.vip_rating
self.hospital.num_vips_ty = self.hospital.num_vips_ty + 1
end
@@ -482,7 +509,42 @@ function Vip:afterLoad(old, new)
if old < 79 then
self.name = self.hospital.visitingVIP
end
if old < 144 then
self.vip_rating = 12 - math.random(0,5)
-- Make sure we only rate rooms from now on if a VIP was visiting
self.room_eval = 0
self.num_visited_rooms = 0
-- VIP's room visit chance is 50% if total rooms in hospital is less than 80 (makes a math.random with 0 and 1 possibilities).
-- Else decided by total rooms / 40 (0, 1, 2 [33%]; 0, 1, 2, 3 [25%] etc)
local rooms_threshold = 79
if #self.world.rooms > rooms_threshold then
self.room_visit_chance = math.floor(#self.world.rooms / 40)
else
self.room_visit_chance = 1
end
-- If our hospital has more patients than counted visitors adjust enter_patients
self.enter_patients = #self.hospital.patients + self.enter_visitors - self.hospital.num_visitors
if self.enter_patients < 0 then
self.enter_patients = 0
end
if self.going_home then
--award max from old code if VIP leaving
self.vip_rating = 2
self.cash_reward = 2000
self.rep_reward = 45
self.vip_message = 2
end
for i, action in ipairs(self.action_queue) do
if action.name == 'idle' and action.loop_callback and (self.waiting > 1 or i > 1) then
action:setCount(50):setAfterUse(action.loop_callback)
action.loop_callback = nil
self.waiting = nil
if i == 1 then
self:queueAction(action, 1)
self:finishAction()
end
end
end
end
Humanoid.afterLoad(self, old, new)
end

View File

@@ -20,19 +20,23 @@ SOFTWARE. --]]
local TH = require("TH")
corsixth.require("announcer")
local AnnouncementPriority = _G["AnnouncementPriority"]
--! An `Object` which needs occasional repair (to prevent explosion).
class "Machine" (Object)
---@type Machine
local Machine = _G["Machine"]
function Machine:Machine(world, object_type, x, y, direction, etc)
function Machine:Machine(hospital, object_type, x, y, direction, etc)
self.total_usage = -1 -- Incremented in the constructor of Object.
self:Object(world, object_type, x, y, direction, etc)
self:Object(hospital, object_type, x, y, direction, etc)
if object_type.default_strength then
-- Only for the main object. The slave doesn't need any strength
local progress = world.ui.hospital.research.research_progress[object_type]
local progress = self.world.ui.hospital.research.research_progress[object_type]
self.strength = progress.start_strength
end
@@ -60,6 +64,24 @@ function Machine:getRemainingUses()
return self.strength - self.times_used
end
--! Returns true if a machine is smoking/needs repair
function Machine:isBreaking()
local threshold = self:getRemainingUses()
return threshold < 4
end
--! Announces a machine needing repair
--!param room The room of the machine
function Machine:announceRepair(room)
local sound = room.room_info.handyman_call_sound
local earthquake = self.world.next_earthquake
self.world.ui:playAnnouncement("machwarn.wav", AnnouncementPriority.Critical)
-- If an earthquake is happening don't play the call sound to prevent spamming
if earthquake.active and earthquake.warning_timer == 0 then return end
-- TODO: Don't announce handyman call sound if there are no handymen
if sound then self.world.ui:playAnnouncement(sound, AnnouncementPriority.Critical) end
end
--! Set whether the smoke animation should be showing
local function setSmoke(self, isSmoking)
-- If turning smoke on for this machine
@@ -99,9 +121,31 @@ function Machine:machineUsed(room)
local threshold = self:getRemainingUses()
-- Find a queued task for a handyman coming to repair this machine
local taskIndex = self.hospital:getIndexOfTask(self.tile_x, self.tile_y, "repairing")
-- Too late it is about to explode
local num_extinguishers = 0
local explosion_chance
local explode = false
-- Room is set to explode
if threshold < 1 then
-- If a fire extinguisher in the room, room has chance not to explode
for object, _ in pairs(room.objects) do
if object.object_type.id == "extinguisher" and num_extinguishers < 4 then
num_extinguishers = num_extinguishers + 1
end
end
if num_extinguishers == 0 or threshold < -3 then
-- If no extinguisher in room, or machine used 5 times over its strength always explode
explode = true
else
-- Explosion chance increases 20% with every use over strength, and reduced by 5% for every additional extinguisher (up to 3 extra) in the room bar the first one
explosion_chance = (2 / self.strength) + (threshold * -0.2) - (num_extinguishers * 0.05) + 0.05
-- Cap it until guaranteed explosion
explosion_chance = explosion_chance > 0.95 and 0.95 or explosion_chance
explosion_chance = explosion_chance < 0.05 and 0.05 or explosion_chance
explode = math.random() < explosion_chance
end
end
-- Room failed to be saved, or no extinguishers were present
if explode then
-- Clean up any task of handyman coming to repair the machine
self.hospital:removeHandymanTask(taskIndex, "repairing")
-- Blow up the room
@@ -121,14 +165,19 @@ function Machine:machineUsed(room)
-- Clear the icon showing a handyman is coming to repair the machine
self:setRepairing(nil)
return true
-- Else if urgent repair needed
-- Else if urgent repair needed or room didn't explode
elseif threshold < 4 then
-- If the job of repairing the machine isn't queued, queue it now (higher priority)
if taskIndex == -1 then
local call = self.world.dispatcher:callForRepair(self, true, false, true)
self.hospital:addHandymanTask(self, "repairing", 2, self.tile_x, self.tile_y, call)
self:announceRepair(room)
else -- Otherwise the task is already queued. Increase the priority to above that of machines with at least 4 uses left
self.hospital:modifyHandymanTaskPriority(taskIndex, 2, "repairing")
-- Upgrades task from low (1) priority to high (2) priority
if self.hospital:getHandymanTaskPriority(taskIndex, "repairing") == 1 then
self.hospital:modifyHandymanTaskPriority(taskIndex, 2, "repairing")
self:announceRepair(room)
end
end
-- Else if repair is needed, but not urgently
elseif threshold < 6 then
@@ -153,11 +202,8 @@ function Machine:calculateSmoke(room)
-- How many uses this machine has left until it explodes
local threshold = self:getRemainingUses()
-- If now exploding, clear any smoke
if threshold < 1 then
setSmoke(self, false)
-- Else if urgent repair needed
elseif threshold < 4 then
-- Machines needing urgent repair show smoke
if threshold < 4 then
-- Display smoke, up to three animations per machine
-- i.e. < 4 one plume, < 3 two plumes or < 2 three plumes of smoke
setSmoke(self, true)

View File

@@ -37,7 +37,9 @@ function Object:getDrawingLayer()
return 4
end
function Object:Object(world, object_type, x, y, direction, etc)
function Object:Object(hospital, object_type, x, y, direction, etc)
assert(class.is(hospital, Hospital), "First argument is not a Hospital instance.")
local th = TH.animation()
self:Entity(th)
@@ -51,8 +53,8 @@ function Object:Object(world, object_type, x, y, direction, etc)
self.ticks = object_type.ticks
self.object_type = object_type
self.world = world
self.hospital = world:getLocalPlayerHospital()
self.hospital = hospital
self.world = hospital.world
self.user = false
self.times_used = -1 -- Incremented in the call on the next line
self:updateDynamicInfo()
@@ -118,8 +120,8 @@ function Object.slaveMixinClass(class_method_table)
local super_constructor = super[class.name(super)]
-- Constructor
class_method_table[name] = function(self, world, object_type, x, y, direction, ...)
super_constructor(self, world, object_type, x, y, direction, ...)
class_method_table[name] = function(self, hospital, object_type, x, y, direction, ...)
super_constructor(self, hospital, object_type, x, y, direction, ...)
if object_type.slave_id then
local orientation = object_type.orientations
orientation = orientation and orientation[direction]
@@ -127,7 +129,7 @@ function Object.slaveMixinClass(class_method_table)
x = x + orientation.slave_position[1]
y = y + orientation.slave_position[2]
end
self.slave = world:newObject(object_type.slave_id, x, y, direction, ...)
self.slave = hospital.world:newObject(object_type.slave_id, x, y, direction, ...)
self.slave.master = self
end
end

View File

@@ -74,21 +74,27 @@ function FileSystem:_enumerate()
end
end
--! Test if file name has an .iso or .dmg extension
function FileSystem:isIso(name)
if name == nil then
return false
end
local ext = name:lower():match("%.(.+)$")
return ext == 'iso' or ext == 'iso9660$' or ext == 'dmg'
end
--! Set the root physical path for this FileSystem.
-- If the path is an ISO then set the provider. If the path is a directory
-- then set the pysical_path and populate the files and sub_dirs.
-- then set the physical_path and populate the files and sub_dirs.
--
--!param physical_path (string) a path on the filesystem to either a directory
-- or theme hospital ISO file.
function FileSystem:setRoot(physical_path)
if physical_path:match"%.[iI][sS][oO]$" or physical_path:match"%.[iI][sS][oO]9660$" then
if self:isIso(physical_path) then
self.provider = ISO_FS()
self.provider:setPathSeparator(pathsep)
local file, err = io.open(physical_path, "rb")
if not file then
return nil, err
end
return self.provider:setRoot(file)
return self.provider:setRoot(physical_path)
end
if physical_path:sub(-1) == pathsep then

View File

@@ -107,7 +107,7 @@ function GameUI:setupGlobalKeyHandlers()
[tostring(self.app.hotkeys["ingame_scroll_up"])] = {x = 0, y = -10},
[tostring(self.app.hotkeys["ingame_scroll_down"])] = {x = 0, y = 10},
[tostring(self.app.hotkeys["ingame_scroll_left"])] = {x = -10, y = 0},
[tostring(self.app.hotkeys["ingame_scroll_right"])] = {x = 10, y = 0},
[tostring(self.app.hotkeys["ingame_scroll_right"])] = {x = 10, y = 0},
}
-- This is the long version of the shift speed key.
@@ -608,7 +608,8 @@ function GameUI:onMouseMove(x, y, dx, dy)
--map.th:setCell(highlight_x, highlight_y, 4, 0)
highlight_x = nil
end
if 1 <= wx and wx <= 128 and 1 <= wy and wy <= 128 then
local map_width, map_height = map.th:size()
if 1 <= wx and wx <= map_width and 1 <= wy and wy <= map_height then
if map.th:getCellFlags(wx, wy).passable then
--map.th:setCell(wx, wy, 4, 24 + 8 * 256)
highlight_x = wx
@@ -643,10 +644,27 @@ function GameUI:onMouseUp(code, x, y)
else -- No room chosen yet, but about to edit one.
if button == "left" then -- Take the clicked one.
local room = self.app.world:getRoom(self:ScreenToWorld(x, y))
if room and not room.crashed then
self:setCursor(self.waiting_cursor)
self.edit_room = room
room:tryToEdit()
if room then
if not room.crashed then
self:setCursor(self.waiting_cursor)
self.edit_room = room
room:tryToEdit()
else
if self.app.config.remove_destroyed_rooms then
local room_cost = room:calculateRemovalCost()
self:setEditRoom(false)
-- show confirmation dialog for removing the room
self:addWindow(UIConfirmDialog(self,_S.confirmation.remove_destroyed_room:format(room_cost),
--[[persistable:remove_destroyed_room_confirm_dialog]]function()
local world = room.world
UIEditRoom:removeRoom(false, room, world)
world:resetSideObjects()
world.rooms[room.id] = nil
self.hospital:spendMoney(room_cost, _S.transactions.remove_room)
end
))
end
end
end
else -- right click, we don't want to edit a room after all.
self:setEditRoom(false)
@@ -1158,7 +1176,7 @@ function GameUI:setEditRoom(enabled)
-- If the actual editing hasn't started yet but is on its way,
-- activate the room again.
if class.is(self.edit_room, Room) and self.cursor == self.waiting_cursor then
self.edit_room.is_active = true
self.app.world:markRoomAsBuilt(self.edit_room)
else
-- If we are currently editing a room it may happen that we need to abort it.
-- Also remove any dialog where the user is buying items.

View File

@@ -127,6 +127,8 @@ function Graphics:loadFontFile()
local windir = os.getenv("WINDIR")
if windir and windir ~= "" then
font_file = windir .. pathsep .. "Fonts" .. pathsep .. "ARIALUNI.TTF"
elseif self.app.os == "macos" then
font_file = "/Library/Fonts/Arial Unicode.ttf"
else
font_file = "/usr/share/fonts/truetype/arphic/uming.ttc"
end
@@ -393,6 +395,8 @@ end
function Graphics:loadFont(sprite_table, x_sep, y_sep, ...)
-- Allow (multiple) arguments for loading a sprite table in place of the
-- sprite_table argument.
-- TODO: Native number support for e.g. Korean languages. Current use of load_font is a stopgap solution for #1193 and should be eventually removed
local load_font = x_sep
if type(sprite_table) == "string" then
local arg = {sprite_table, x_sep, y_sep, ...}
local n_pass_on_args = #arg
@@ -411,7 +415,8 @@ function Graphics:loadFont(sprite_table, x_sep, y_sep, ...)
end
local use_bitmap_font = true
if not sprite_table:isVisible(46) then -- uppercase M
-- Force bitmap font for the moneybar (Font05V)
if not sprite_table:isVisible(46) or load_font == "Font05V" then -- uppercase M
-- The font doesn't contain an uppercase M, so (in all likelihood) is used
-- for drawing special symbols rather than text, so the original bitmap
-- font should be used.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,55 @@
--[[ Copyright (c) 2020 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. --]]
class "AIHospital" (Hospital)
---@type AIHospital
local AIHospital = _G["AIHospital"]
function AIHospital:AIHospital(competitor, world, avail_rooms, name)
self:Hospital(world, avail_rooms, name)
if name then
self.name = name
elseif _S.competitor_names[competitor] then
self.name = _S.competitor_names[competitor]
else
self.name = "NONAME"
end
self.is_in_world = false
-- AI Hospitals can't cheat, so don't let them
self.hosp_cheats = nil
end
function AIHospital:spawnPatient()
-- TODO: Simulate patient
end
function AIHospital:logTransaction()
-- AI doesn't need a log of transactions, as it is only used for UI purposes
end
function AIHospital:afterLoad(old, new)
if old < 145 then
self.hosp_cheats = nil
end
Hospital.afterLoad(self, old, new)
end

View File

@@ -0,0 +1,425 @@
--[[ Copyright (c) 2020 Albert "Alberth" Hofkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. --]]
class "PlayerHospital" (Hospital)
---@type PlayerHospital
local PlayerHospital = _G["PlayerHospital"]
local num_sitting_ratios = 15 -- Number of stored recent sitting ratio measurements.
local ratio_interval = 2 -- Measurement interval in days.
function PlayerHospital:PlayerHospital(world, avail_rooms, name)
self:Hospital(world, avail_rooms, name)
-- The player hospital in single player can access the Cheat System should they wish to.
self.hosp_cheats = Cheats(self)
self.adviser_data = { -- Variables handling player advice.
temperature_advice = nil, -- Whether the player received advice about room temp.
reception_advice = nil, -- Whether advice was given about building the reception.
cured_died_message = nil, -- Whether the adviser reported about a cure or death.
sitting_ratios = {}, -- Measurements of recent sitting/standing ratios.
sitting_index = 1 -- Next entry in 'sitting_ratios' to update.
}
self.win_declined = false -- Has not yet declined the level win fax
end
--! Give advice to the player at the end of a day.
function PlayerHospital:dailyAdviceChecks()
local current_date = self.world:date()
local day = current_date:dayOfMonth()
-- Hold any advice back until the game has somewhat started.
if current_date < Date(1, 5) then
return
end
-- Warn about lack of a staff room.
if day == 3 and self:countRoomOfType("staff_room", 1) == 0 then
local staffroom_advice = {
_A.warnings.build_staffroom, _A.warnings.need_staffroom,
_A.warnings.staff_overworked, _A.warnings.staff_tired,
}
self:giveAdvice(staffroom_advice)
end
-- Warn about lack of toilets.
if day == 8 and self:countRoomOfType("toilets", 1) == 0 then
local toilet_advice = {
_A.warnings.need_toilets, _A.warnings.build_toilets,
_A.warnings.build_toilet_now,
}
self:giveAdvice(toilet_advice)
end
-- Make players more aware of the need for radiators
if self:countRadiators() == 0 then
self:giveAdvice({_A.information.initial_general_advice.place_radiators})
end
-- Verify patients well-being with respect to room temperature.
if day == 15 and not self.adviser_data.temperature_advice
and not self.heating.heating_broke then
-- Check patients warmth, default value does not result in a message.
local warmth = self:getAveragePatientAttribute("warmth", 0.3)
if warmth < 0.22 then
local cold_advice = {
_A.information.initial_general_advice.increase_heating,
_A.warnings.patients_very_cold, _A.warnings.people_freezing,
}
self:giveAdvice(cold_advice)
self.adviser_data.temperature_advice = true
elseif warmth >= 0.36 then
local hot_advice = {
_A.information.initial_general_advice.decrease_heating,
_A.warnings.patients_too_hot, _A.warnings.patients_getting_hot,
}
self:giveAdvice(hot_advice)
self.adviser_data.temperature_advice = true
end
end
-- Verify staff well-being with respect to room temperature.
if day == 20 and not self.adviser_data.temperature_advice
and not self.heating.heating_broke then
-- Check staff warmth, default value does not result in a message.
local warmth = self:getAverageStaffAttribute("warmth", 0.25)
if warmth < 0.22 then
self:giveAdvice({_A.warnings.staff_very_cold})
self.adviser_data.temperature_advice = true
elseif warmth >= 0.36 then
self:giveAdvice({_A.warnings.staff_too_hot})
self.adviser_data.temperature_advice = true
end
end
-- Are there sufficient drinks available?
if day == 24 then
-- Check patients thirst, default value does not result in a message.
local thirst = self:getAveragePatientAttribute("thirst", 0)
-- Increase need after the first year.
local threshold = current_date:year() == 1 and 0.9 or 0.8
if thirst > threshold then
self:giveAdvice({_A.warnings.patients_very_thirsty})
elseif thirst > 0.6 then
local thirst_advice = {
_A.warnings.patients_thirsty, _A.warnings.patients_thirsty2,
}
self:giveAdvice(thirst_advice)
end
end
-- Track sitting / standing ratio of patients.
if day % ratio_interval == 0 then
-- Compute the ratio of today.
local num_sitting, num_standing = self:countSittingStanding()
local ratio = (num_sitting + num_standing > 10)
and num_sitting / (num_sitting + num_standing) or nil
-- Store the measured ratio.
self.adviser_data.sitting_ratios[self.adviser_data.sitting_index] = ratio
self.adviser_data.sitting_index = (self.adviser_data.sitting_index >= num_sitting_ratios)
and 1 or self.adviser_data.sitting_index + 1
end
-- Check for enough (well-placed) benches.
if day == 12 then
-- Compute average sitting ratio.
local sum_ratios = 0
local index = 1
while index <= num_sitting_ratios do
local ratio = self.adviser_data.sitting_ratios[index]
if ratio == nil then
sum_ratios = nil
break
else
sum_ratios = sum_ratios + ratio
end
index = index + 1
end
if sum_ratios ~= nil then -- Sufficient data available.
local ratio = sum_ratios / num_sitting_ratios
if ratio < 0.7 then -- At least 30% standing.
local bench_advice = {
_A.warnings.more_benches, _A.warnings.people_have_to_stand,
}
self:giveAdvice(bench_advice)
elseif ratio > 0.9 then
-- Praise having enough well placed seats about once a year.
local bench_advice = {
_A.praise.many_benches, _A.praise.plenty_of_benches,
_A.praise.few_have_to_stand,
}
self:giveAdvice(bench_advice, 1/12)
end
end
end
if day == 10 then
self:warnForLongQueues()
end
-- Reset advise flags at the end of the month.
if day == 28 then
self.adviser_data.temperature_advice = false
end
end
--! Give advice to the player at the end of a month.
function PlayerHospital:monthlyAdviceChecks()
local today = self.world:date()
local current_month = today:monthOfYear()
local current_year = today:year()
-- Check for advice on money.
if not self.world.free_build_mode then
if self.balance < 2000 and self.balance >= -500 then
local cashlow_advice = {
_A.warnings.money_low, _A.warnings.money_very_low_take_loan,
_A.warnings.cash_low_consider_loan,
}
self:giveAdvice(cashlow_advice)
elseif self.balance < -2000 and current_month > 8 then
-- TODO: Ideally this should be linked to the lose criteria for balance.
self:giveAdvice({_A.warnings.bankruptcy_imminent})
elseif self.balance > 6000 and self.loan > 0 then
self:giveAdvice({_A.warnings.pay_back_loan})
end
end
self:checkReceptionAdvice(current_month, current_year)
end
--! Make players aware of the need for a receptionist and desk.
--!param current_month (int) Month of the year.
--!param current_year (int) Current game year.
function PlayerHospital:checkReceptionAdvice(current_month, current_year)
if current_year > 1 then return end -- Playing too long.
if self:hasStaffedDesk() then return end -- Staffed desk available, all done.
local num_receptionists = self:countStaffOfCategory("Receptionist", 1)
if num_receptionists ~= 0 and current_month > 2 and not self.adviser_data.reception_advice then
self:giveAdvice({_A.warnings.no_desk_6})
self.adviser_data.reception_advice = true
elseif num_receptionists == 0 and current_month > 2 and self:countReceptionDesks() ~= 0 then
self:giveAdvice({_A.warnings.no_desk_7})
elseif current_month == 3 then
self:giveAdvice({_A.warnings.no_desk}, 1, true)
elseif current_month == 8 then
self:giveAdvice({_A.warnings.no_desk_1}, 1, true)
elseif current_month == 11 then
if self.visitors == 0 then
self:giveAdvice({_A.warnings.no_desk_2}, 1, true)
else
self:giveAdvice({_A.warnings.no_desk_3}, 1, true)
end
end
end
--! Give advice to the user about having bought a reception desk.
function PlayerHospital:msgReceptionDesk()
local num_receptionists = self:countStaffOfCategory("Receptionist", 1)
if not self.world.ui.start_tutorial and num_receptionists == 0 then
self:giveAdvice({_A.room_requirements.reception_need_receptionist})
elseif num_receptionists > 0 and self:countReceptionDesks() == 1 and
not self.adviser_data.reception_advice and self.world:date():monthOfGame() > 3 then
self:giveAdvice({_A.warnings.no_desk_5})
self.adviser_data.reception_advice = true
end
end
--! Give advice to the user about maintenance of plants.
function PlayerHospital:msgPlant()
local num_handyman = self:countStaffOfCategory("Handyman", 1)
if num_handyman == 0 then
self:giveAdvice({_A.staff_advice.need_handyman_plants})
end
end
--! Show the 'Gates to hell' animation.
--!param entity (Entity) Gates to hell.
function PlayerHospital:showGatesToHell(entity)
local anim_func = --[[persistable:lava_hole_spawn_animation_end]]
function(anim_entity)
anim_entity:setAnimation(1602)
end
entity:playEntitySounds("LAVA00*.WAV", {0,1350,1150,950,750,350},
{0,1450,1250,1050,850,450}, 40)
entity:setTimer(entity.world:getAnimLength(2550), anim_func)
entity:setAnimation(2550)
end
--! Advises the player.
--!param msgs (array of string) Messages to select from.
--!param rnd_frac (optional float in range (0, 1]) Fraction of times that the
-- call actually says something.
--!param stay_up (bool) If true, let the adviser remain visible afterwards.
--!return (boolean) Whether a message was given to the user.
function PlayerHospital:giveAdvice(msgs, rnd_frac, stay_up)
local max_rnd = #msgs
if rnd_frac and rnd_frac > 0 and rnd_frac < 1 then
-- Scale by the fraction.
max_rnd = math.floor(max_rnd / rnd_frac)
end
local index = (max_rnd == 1) and 1 or math.random(1, max_rnd)
if index <= #msgs then
self.world.ui.adviser:say(msgs[index], stay_up)
return true
end
return false
end
--! Give the user possibly a message about a cured patient.
function PlayerHospital:msgCured()
self.world.ui:playSound("cheer.wav") -- This sound is always heard
if self.num_cured < 1 then -- First cure is always reported.
self:giveAdvice({_A.information.first_cure})
elseif self.num_cured > 1 and not self.adviser_data.cured_died_message then
local cured_msgs = {
_A.level_progress.another_patient_cured:format(self.num_cured),
_A.praise.patients_cured:format(self.num_cured)
}
self.adviser_data.cured_died_message = self:giveAdvice(cured_msgs, 2/15)
end
end
--! Give the user possibly a message about a dead patient.
function PlayerHospital:msgKilled()
self.world.ui:playSound("boo.wav") -- this sound is always heard
if self.num_deaths < 1 then -- First death is always reported.
self:giveAdvice({_A.information.first_death})
elseif self.num_deaths > 1 and not self.adviser_data.cured_died_message then
local died_msgs = {
_A.warnings.many_killed:format(self.num_deaths),
_A.level_progress.another_patient_killed:format(self.num_deaths)
}
self.adviser_data.cured_died_message = self:giveAdvice(died_msgs, 6/10)
end
end
--! Once a month the advisor may warn about long queues.
--! Rooms requiring a doctor occasionally trigger the generic message
function PlayerHospital:warnForLongQueues()
local queue_rooms, total_queue = {}, 0
for _, room in pairs(self.world.rooms) do
if #room.door.queue then
total_queue = total_queue + #room.door.queue
end
if #room.door.queue > 7 then
queue_rooms[#queue_rooms + 1] = room
end
end
if #queue_rooms == 0 or total_queue == 0 then return end
local busy_threshold = 1.5 * total_queue / #self.world.rooms
local chosen_room = queue_rooms[math.random(1, #queue_rooms)]
if busy_threshold > #chosen_room.door.queue then return end
chosen_room = chosen_room.room_info
-- Required staff that is not nurse is doctor, researcher, surgeon or psych
if chosen_room.required_staff and not chosen_room.required_staff["Nurse"]
and math.random(1, 3) > 1 then
local warn_msgs = {
_A.warnings.queue_too_long_send_doctor:format(chosen_room.name),
_A.staff_advice.need_doctors
}
self:giveAdvice(warn_msgs)
else
self.world.ui.adviser:say(_A.warnings.queues_too_long)
end
end
--! Called at the end of each day.
function PlayerHospital:onEndDay()
-- Advise the player.
if self:hasStaffedDesk() then
self:dailyAdviceChecks()
end
Hospital.onEndDay(self)
end
-- Called at the end of each day.
function PlayerHospital:onEndMonth()
-- Advise the player on cash flow.
if self:hasStaffedDesk() then
self:monthlyAdviceChecks()
end
self.adviser_data.cured_died_message = nil -- Enable the message again.
-- Check if a player has won the level at months 3, 6 and 9. The annual report
-- window will perform this check at month 12 when it has been closed.
-- If the offer is declined then the next check is at month 6 and the annual report.
local check_months = {
[3] = not self.win_declined,
[6] = true,
[9] = not self.win_declined
}
if check_months[self.world.game_date:monthOfYear()] then self.world:checkIfGameWon() end
Hospital.onEndMonth(self)
end
function PlayerHospital:afterLoad(old, new)
if old < 145 then
self.hosp_cheats = Cheats(self)
end
if old < 146 then
self.adviser_data = {
temperature_advise = nil,
sitting_ratios = {},
sitting_index = 1
}
end
if old < 147 then
-- Copy value of the previous name of the variable.
self.adviser_data.reception_advice = self.receptionist_msg
end
if old < 148 then
self.adviser_data.cured_died_message = nil
end
if old < 149 then
self.win_declined = false -- Has not yet declined the level win fax
end
Hospital.afterLoad(self, old, new)
end

View File

@@ -102,8 +102,8 @@ local action_die_tick_reaper; action_die_tick_reaper = permanent"action_die_tick
local mirror_grim = 0
local spawn_scenarios = {
{"south", humanoid.tile_x, humanoid.tile_y + 4, 0, 1, "north", 0, -1, {{after_spawn_idle_direction = "east", hole_x_offset = -5, hole_y_offset = 2}, {hole_x_offset = 0, hole_y_offset = 3}} },
{"east", humanoid.tile_x + 4, humanoid.tile_y, 1, 0, "west", -1, 0, {{hole_x_offset = 3, hole_y_offset = 0}} }
{"south", humanoid.tile_x, humanoid.tile_y + 4, 1, 0, "west", -1, 0, {{after_spawn_idle_direction = "east", hole_x_offset = -5, hole_y_offset = 2}, {hole_x_offset = 0, hole_y_offset = 3}} },
{"east", humanoid.tile_x + 4, humanoid.tile_y, 0, 1, "north", 0, -1, {{hole_x_offset = 3, hole_y_offset = 0}} }
}
---
@@ -114,15 +114,20 @@ local action_die_tick_reaper; action_die_tick_reaper = permanent"action_die_tick
hole_x, hole_y = humanoid.world.pathfinder:findIdleTile(spawn_scenario[2], spawn_scenario[3], 0)
if hole_x and humanoid.world:canNonSideObjectBeSpawnedAt(hole_x, hole_y, "gates_to_hell", holes_orientation, 0, 0) then
if holes_orientation == "east" then
if holes_orientation == "south" then
mirror_grim = 1
end
grim_use_tile_x = hole_x + spawn_scenario[4]
grim_use_tile_y = hole_y + spawn_scenario[5]
humanoid.hole_use_tile_x = hole_x + spawn_scenario[7]
humanoid.hole_use_tile_y = hole_y + spawn_scenario[8]
-- tile can't be in a room
if humanoid.world:getRoom(humanoid.hole_use_tile_x, humanoid.hole_use_tile_y) then
-- tile can't be in a room and must be accessible by the patient
if not humanoid.world:getPathDistance(humanoid.tile_x, humanoid.tile_y, humanoid.hole_use_tile_x, humanoid.hole_use_tile_y)
or humanoid.world:getRoom(humanoid.hole_use_tile_x, humanoid.hole_use_tile_y) then
return false
end
-- ensure grim won't be in a room
if humanoid.world:getRoom(grim_use_tile_x, grim_use_tile_y) then
return false
end
--Ensure that the lava hole is passable on at least one of its sides to prevent it from blocking 1 tile wide corridors:
@@ -137,7 +142,8 @@ local action_die_tick_reaper; action_die_tick_reaper = permanent"action_die_tick
for _, find_grim_spawn_attempt in ipairs(spawn_scenario[9]) do
grim_spawn_idle_direction = find_grim_spawn_attempt.after_spawn_idle_direction or spawn_scenario[6]
grim_x, grim_y = humanoid.world.pathfinder:findIdleTile(hole_x + find_grim_spawn_attempt.hole_x_offset, hole_y + find_grim_spawn_attempt.hole_y_offset, 0)
if grim_x and not humanoid.world:getRoom(grim_x, grim_y) then
if grim_x and not humanoid.world:getRoom(grim_x, grim_y)
and humanoid.world:getPathDistance(grim_x, grim_y, grim_use_tile_x, grim_use_tile_y) then
grim_cant_walk_to_use_tile = false
break
end

View File

@@ -208,7 +208,7 @@ local action_queue_on_change_position = permanent"action_queue_on_change_positio
local queue = action.queue
if not must_stand then
for i = 1, queue.bench_threshold do
if queue[i] == humanoid then
if queue:reportedHumanoid(i) == humanoid then
must_stand = true
break
end

View File

@@ -38,6 +38,7 @@ local function action_seek_reception_start(action, humanoid)
local world = humanoid.world
local best_desk
local score
local queuetotal = 0
assert(humanoid.hospital, "humanoid must be associated with a hospital to seek reception")
@@ -62,6 +63,7 @@ local function action_seek_reception_start(action, humanoid)
score = this_score
best_desk = desk
end
queuetotal = queuetotal + #desk.queue
end
end
if best_desk then
@@ -93,6 +95,19 @@ local function action_seek_reception_start(action, humanoid)
end
end
end
local desks = #humanoid.hospital:findReceptionDesks()
local receptionists = humanoid.hospital:countStaffOfCategory("Receptionist")
if (receptionists > 1 and desks > 0) or (receptionists > 0 and desks > 1) then
local queueavg = math.floor(queuetotal / desks)
if receptionists < desks and queueavg > 5 then
world.ui.adviser:say(_A.warnings.reception_bottleneck)
elseif queueavg > 4 then
world.ui.adviser:say(_A.warnings.queue_too_long_at_reception)
elseif receptionists > desks then
world.ui.adviser:say(_A.warnings.another_desk)
end
end
else
-- No reception desk found. One will probably be built soon, somewhere in
-- the hospital, so either walk to the hospital, or walk around the hospital.

View File

@@ -74,7 +74,7 @@ local action_seek_room_find_room = permanent"action_seek_room_find_room"( functi
table.remove(available_rooms, room_at_index)
-- If the room can be built, set the flag for it.
local diag = humanoid.world.available_rooms[room_type]
if diag and humanoid.hospital.discovered_rooms[diag] then
if diag and humanoid.hospital:isRoomDiscovered(diag.id) then
action.diagnosis_exists = room_type
end
end
@@ -135,7 +135,8 @@ local function action_seek_room_no_treatment_room_found(room_type, humanoid)
local req = humanoid.hospital:checkDiseaseRequirements(humanoid.disease.id)
local research_enabled = false
if req then
research_enabled = humanoid.hospital:hasRoomOfType("research") and humanoid.hospital:hasStaffOfCategory("Researcher")
research_enabled = (humanoid.hospital:countRoomOfType("research", 1) > 0 and
humanoid.hospital:countStaffOfCategory("Researcher", 1) > 0)
if #req.rooms == 1 then
local room_name, required_staff, staff_name = humanoid.world:getRoomNameAndRequiredStaffName(req.rooms[1])
if req.staff[required_staff] or 0 > 0 then
@@ -273,10 +274,10 @@ local function action_seek_room_start(action, humanoid)
-- check we can treat the patient - and just shortcut the processing
local req = humanoid.hospital:checkDiseaseRequirements(humanoid.disease.id)
if humanoid.diagnosed and not req then
if humanoid.diagnosed then
local room = action_seek_room_find_room(action, humanoid)
-- if we have the room but not the staff we shouldn't seek out the room either
if room then
if room and (not req or not action.treatment_room) then
if humanoid.message then
TheApp.ui.bottom_panel:removeMessage(humanoid)
end
@@ -357,7 +358,7 @@ local function action_seek_room_start(action, humanoid)
-- don't need this as we unregistered all previous callbacks if we went to research
local room_req = humanoid.hospital:checkDiseaseRequirements(humanoid.disease.id)
-- get required staff
if not humanoid.diagnosed or not room_req then
if not humanoid.diagnosed or (not room_req or not action.treatment_room) then
action_seek_room_goto_room(rm, humanoid, action.diagnosis_room)
TheApp.ui.bottom_panel:removeMessage(humanoid)
humanoid:unregisterRoomBuildCallback(build_callback)

View File

@@ -50,7 +50,7 @@ local function action_vip_go_to_next_room_start(action, humanoid)
humanoid.next_room.humanoids_enroute[humanoid] = nil
--humanoid.next_room.door.reserved_for = humanoid
humanoid:evaluateRoom()
humanoid.waiting = 3
humanoid.waiting = nil
end
-- Find direction to look at
local ix, iy = humanoid.next_room:getEntranceXY(true)
@@ -64,7 +64,7 @@ local function action_vip_go_to_next_room_start(action, humanoid)
dir = "east"
end
end
humanoid:queueAction(IdleAction():setLoopCallback(evaluate):setDirection(dir))
humanoid:queueAction(IdleAction():setCount(50):setAfterUse(evaluate):setDirection(dir))
-- Finish this action and start the above sequence.
humanoid:finishAction()

View File

@@ -58,6 +58,36 @@ vip_names = {
[6] = "O Presidente da Cruz Vermelha",
}
-- An override for the squits becoming the the squits see issue 1646
adviser.research.drug_improved_1 = "Seu Centro de Pesquisa melhorou o medicamento para %s"
-- Disease overrides where there are typos
golf_stones.cure = "Cura - Dois cirurgiöes operam para extrair os cálculos."
ruptured_nodules.cure = "Cura - Dois cirurgiöes qualificados colocam certas partes com mäos firmes."
slack_tongue.cause = "Causa - Fazer muita fofoca."
slack_tongue.cure = "Cura - Coloca-se a língua no corta-línguas e elimina-se de forma rápida, eficaz e dolorosa."
the_squits.cure = "Cura - Uma mistura pegajosa de substâncias viscosas preparada na farmácia solidifica as tripas do paciente."
bloaty_head.cure = "Cura - Fura-se a cabeça inflada, em seguida, volta-se a inflar até o tamanho correto com uma máquina inteligente."
-- Rooms overrides where there are typos
inflation[2] = "Os pacientes que sofrem a dolorosa doença de terem a cabeça inflada devem ir à sala de inflatoterapia, será desinflada sua cabeça e em seguida vota a ser inflada à pressäo correta.//"
staff_room[2] = "Os seus funcionários se cansam quando realizam o seu trabalho. Precisam desta sala para descansar e se refrescar. Os funcionários cansados säo mais lentos, pedem mais dinheiro e por fim väo embora. Também cometem mais erros. Vale a pena construir uma sala que tenham muitos passatempos. Assegure-se de que há lugar para vários funcionários ao mesmo tempo. "
-- Staff description overrides where there are typos
staff_descriptions.bad[14] = "Pessoa ardilosa, matreira e subversiva. "
staff_descriptions.misc[11] = "Destila uísque. "
-- Correction to a pay rise prompt with typos
pay_rise.regular[1] = "Estou esgotado. Preciso de umas boas férias, mais um aumento de %d, se näo quiser deixo este maldito emprego."
-- Level description overrides where there are typos. Note: This is the only portion of the game that SHOULD use double space after fullstops etc.
introduction_texts.level17 = "Um último aviso. Fique de olho na sua Reputaçäo, é o que atrairá pacientes ao seu estabelecimento. Se näo matar muitas pessoas e as mantiver razoavelmente felizes näo deverá ter muitos problemas com este nível!//Agora é com você, boa sorte."
introduction_texts.level11 = "Tem a oportunidade de construir o hospital definitivo. Esta é uma área de enorme prestígio e o Ministério quer que este seja o melhor hospital. Esperamos que ganhe muito dinheiro, alcance uma excelente reputaçäo e que se encarregue de todos os casos que sejam apresentados. Este é um trabalho importante. Terá que ser muito hábil para completá-lo. Também deve ter em conta que foram vistos OVNIs na área. Assegure-se de que os seus funcionários estejam preparados para receber algumas visitas inesperadas. O seu hospital terá que alcançar um valor de $240.000, precisará ter $500.000 no banco e uma reputaçäo de 700."
introduction_texts.level9 = "Depois de depositar o dinheiro na conta bancária do Ministério e pagar uma nova limousine para o Ministro, agora pode dedicar a criar um bom hospital para cuidar dos doentes e necessitados. Aqui terá um montäo de problemas diferentes. Se os seus funcionários tiverem uma boa formaçäo e suficientes consultas, poderäo resolver qualquer situaçäo. O seu hospital terá que valer $200.000 e precisará ter $400.000 no banco. Se näo o conseguir näo poderá terminar o nível."
introduction_texts.level16 = "Uma vez que tenha diagnosticado algum dos pacientes, precisará construir salas de tratamento e clínicas para curá-los. Pode começar com uma Farmácia, onde precisará de uma Enfermeira que distribua os remédios."
introduction_texts.level10 = "Além de dever curar todas as doenças que possa haver, o Ministério pede que empregue um pouco de tempo em aumentar a eficácia dos seus remédios.//Houve algumas queixas por parte do D. Salutíssimo, o Cachorro Guardiäo da Saúde, assim deve procurar com que todos os seus remédios sejam extremamente eficazes para ficar bem. Também, assegure-se de que o seu hospital tenha uma reputaçäo irrepreensível. Procure deixar morrer poucos pacientes. Como sugestäo, deveria deixar espaço para um banho gelatinoso. Para ganhar, os seus remédios deveräo ter uma eficácia de, pelo menos, 80%, tem que conseguir uma reputaçäo de 650 e guardar $500.000 no banco."
introduction_texts.level12 = "Agora enfrentará o maior dos desafios. Impressionado com os seus lucros, o Ministério tem uma grande tarefa para você; querem que construa outro magnífico hospital, que tenha um excelente lucro e uma reputaçäo incrível. Também querem que compre todo o terreno que puder, cure tudo (e queremos dizer todas as doenças) e ganhe todos os prêmios. Acha que conseguirá? Ganhe $650.000, cure 750 pessoas e consiga uma reputaçäo de 800 para ganhar este nível."
introduction_texts.level15 = "Bem, estes säo os mecanismos básicos para pôr em funcionamento um hospital.//Os seus Médicos väo precisar de toda a ajuda que possam obter para diagnosticar alguns dos pacientes. Pode ajudá-los construindo outros equipamentos de diagnóstico como a sala de Diagnóstico Geral."
-- A small error in the introduction text of level 2
introduction_texts.level2 = "Há uma grande variedade de doenças nesta área. Prepare o seu hospital para tratar mais pacientes " ..
"e para a construçäo de um Centro de Pesquisa. Lembre-se que deve manter limpo o hospital e procurar atingir a " ..
@@ -66,8 +96,19 @@ introduction_texts.level2 = "Há uma grande variedade de doenças nesta área. P
"ser pesquisadas antes de as construir. Também pode comprar mais terreno para aumentar o seu hospital. " ..
"Para isso, utilize o mapa da cidade. Obtenha uma reputaçäo de 300 um saldo de bancário de $10,000 e 40 pessoas curadas. "
-- An override for the squits becoming the the squits see issue 1646
adviser.research.drug_improved_1 = "Seu Centro de Pesquisa melhorou o medicamento para %s"
-- Override for level progress typo
level_progress.hospital_value_enough = "Mantenha o valor do seu hospital acima de %d e atenda os seus outros problemas para ganhar o nível."
level_progress.cured_enough_patients = "Curou muitos pacientes, mas precisa manter o seu hospital em melhor estado para ganhar o nível."
-- Override for multiplayer typos
multiplayer.players_failed = "O(s) seguinte jogador(es) falharam ao alcançar o último objetivo: "
multiplayer.everyone_failed = "Todos falharam ao satisfazer aquele último objetivo."
-- Override for a disease patient choice typo
disease_discovered_patient_choice.need_to_employ = "Contrate um(a) %s para corrigir esta situaçäo."
--Win message override typo
letter[12][2] = "A sua bem-sucedida carreira como o melhor diretor de hospital desde Moisés está chegando ao seu fim. Você provocou tanto efeito nos círculos médicos, que o Ministério quer lhe oferecer um salário de %s, só para inaugurar festas, navios e organizar entrevistas em nosso nome. Você seria um excelente relaçöes públicas!//"
------------------------------- NEW STRINGS -------------------------------
date_format = {
daymonth = "%1% %2:months%",
@@ -113,10 +154,10 @@ menu_options = {
menu_options_game_speed = {
pause = " (%1%) PAUSAR ",
slowest = " (%1%) MUITO LENTO ",
slower = " (%1%) LENTO ",
slower = " (%1%) LENTO ",
normal = " (%1%) NORMAL ",
max_speed = " (%1%) RAPIDO ",
and_then_some_more = " (%1%) MUITO RAPIDO ",
and_then_some_more = " (%1%) MUITO RAPIDO ",
}
menu_options_warmth_colors = {
@@ -127,20 +168,20 @@ menu_options_warmth_colors = {
menu_options_wage_increase = {
grant = " PERMITIR ",
deny = " NEGAR ",
deny = " NEGAR ",
}
-- Add F-keys to entries in charts menu (except briefing), also town_map was added.
menu_charts = {
bank_manager = " (%1%) GERENTE DO BANCO ",
statement = " (%1%) ESTADO DA CONTA ",
bank_manager = " (%1%) GERENTE DO BANCO ",
statement = " (%1%) ESTADO DA CONTA ",
staff_listing = " (%1%) LISTA DOS FUNCIONARIOS ",
town_map = " (%1%) MAPA DA CIDADE ",
casebook = " (%1%) FICHA CLINICA ",
research = " (%1%) PESQUISA ",
status = " (%1%) ESTADO ",
graphs = " (%1%) GRAFICO ",
policy = " (%1%) NORMAS ",
town_map = " (%1%) MAPA DA CIDADE ",
casebook = " (%1%) FICHA CLINICA ",
research = " (%1%) PESQUISA ",
status = " (%1%) ESTADO ",
graphs = " (%1%) GRAFICO ",
policy = " (%1%) NORMAS ",
}
menu_debug = {
@@ -175,10 +216,10 @@ menu_debug_overlay = {
parcel = " PARCELA ",
}
menu_player_count = {
players_1 = " 1 JOGADOR ",
players_2 = " 2 JOGADORES ",
players_3 = " 3 JOGADORES ",
players_4 = " 4 JOGADORES ",
players_1 = " 1 JOGADOR ",
players_2 = " 2 JOGADORES ",
players_3 = " 3 JOGADORES ",
players_4 = " 4 JOGADORES ",
}
adviser = {
room_forbidden_non_reachable_parts = "Construir a sala neste local resultará em alas do hospital que näo poderäo ser acessadas.",
@@ -205,7 +246,7 @@ adviser = {
researcher_needs_desk_2 = "Seu pesquisador agradece que lhe tenha dado um descanso. Se pretende ter mais pessoas pesquisando, deve dar a cada um uma mesa para trabalhar.",
researcher_needs_desk_3 = "Cada pesquisa necessita de uma mesa para trabalhar.",
nurse_needs_desk_1 = "Cada enfermeira necessita de uma mesa para trabalhar.",
nurse_needs_desk_2 = "Sua enfermeira agradece que lhe tenha dado um descanso. Se pretende ter mais pessoas trabalhando na enfermaria, tem que dar a cada uma delas uma mesa para trabalhar.",
nurse_needs_desk_2 = "Sua enfermeira agradece que lhe tenha dado um descanso. Se pretende ter mais pessoas trabalhando na Enfermaria, tem que dar a cada uma delas uma mesa para trabalhar.",
low_prices = "Você está cobrando muito pouco pelo uso de %s. Isto trará pessoas para o seu hospital, mas você näo fará muito lucro com cada uma delas.",
high_prices = "Você está cobrando muito pelo uso de %s. Isto trará grande lucro a curto prazo, mas no final das contas você começará a afugentar as pessoas para longe do seu hospital.",
fair_prices = "O preço de %s parece justo.",
@@ -221,6 +262,7 @@ adviser = {
dynamic_info.patient.actions.no_gp_available = "Esperando que construa um Consultório Geral"
dynamic_info.staff.actions.heading_for = "Dirigindo-se para %s"
dynamic_info.staff.actions.fired = "Despedido"
dynamic_info.staff.actions.vaccine = "Vacinando um paciente"
dynamic_info.patient.actions.epidemic_vaccinated = "Eu näo sou mais contagioso"
progress_report.free_build = "CONSTRUÇAO LIVRE"
@@ -372,6 +414,10 @@ options_window = {
apply = "Aplicar",
cancel = "Cancelar",
back = "Voltar",
scrollspeed = "Veloc. de Rolagem",
shift_scrollspeed = "Veloc. Rolagem Acel.",
zoom_speed = "Veloc. do Zoom",
hotkey = "Atalhos",
}
tooltip.options_window = {
@@ -392,6 +438,16 @@ tooltip.options_window = {
select_language = "Selecione o idioma do jogo",
language_dropdown_item = "Selecionar %s como idioma",
back = "Fechar a janela de Configuraçöes",
scrollspeed = "Defina a velocidade de rolagem entre 1 (mais lenta) até 10 (mais rápida). O padräo é 2.",
shift_scrollspeed = "Defina a velocidade da rolagem enquanto a tecla Shift é pressionada. 1 (mais lento) até 10 (mais rápido). O padräo é 4.",
zoom_speed = "Defina a velocidade da aproximaçäo da câmera de 10 (mais lenta) até 1000 (mais rápida). O padräo é 80.",
apply_scrollspeed = "Aplique a velocidade de rolagem inserida.",
cancel_scrollspeed = "Retornar sem alterar a velocidade de rolagem.",
apply_shift_scrollspeed = "Aplique a velocidade de rolagem de acelerada inserida.",
cancel_shift_scrollspeed = "Retorne sem alterar a velocidade de rolagem acelerada.",
apply_zoomspeed = "Aplique a velocidade de aproximaçäo inserida.",
cancel_zoomspeed = "Retorne sem alterar a velocidade de aproximaçäo.",
hotkey = "Altere as teclas de atalho do teclado.",
}
customise_window = {
@@ -454,6 +510,124 @@ tooltip.folders_window = {
back = "Fechar este menu e voltar para o menu de Configuraçöes",
}
hotkey_window = {
caption_main = "Atribuiçäo de teclas de atalho",
caption_panels = "Teclas do painel",
button_accept = "Aceitar",
button_defaults = "Restaurar padröes",
button_cancel = "Cancelar",
button_back = "Voltar",
button_toggleKeys = "Teclas de alternância",
button_recallPosKeys = "Tecla de retorno de posiçäo",
panel_globalKeys = "Teclas globais",
panel_generalInGameKeys = "Teclas gerais no jogo",
panel_scrollKeys = "Teclas de rolagem",
panel_zoomKeys = "Teclas de aproximaçäo",
panel_gameSpeedKeys = "Teclas de velocidade do jogo",
panel_miscInGameKeys = "Teclas diversas no jogo",
panel_toggleKeys = "Teclas de alternância",
panel_debugKeys = "Teclas de depuraçäo",
panel_storePosKey = "Armazenamento de posiçäo",
panel_recallPosKeys = "Retorno de posiçäo",
panel_altPanelKeys = "Teclas de alternância do painel",
global_confirm = "Confirmar",
global_confirm_alt = "Alt. confirmar",
global_cancel = "Cancelar",
global_cancel_alt = "Alt. cancelar",
global_fullscreen_toggle = "Tela cheia",
global_exitApp = "Sair do app",
global_resetApp = "Reiniciar app",
global_releaseMouse = "Liberar mouse",
global_connectDebugger = "Depurar",
global_showLuaConsole = "Console Lua",
global_runDebugScript = "Script de depuraçäo",
global_screenshot = "Capturar tela",
global_stop_movie_alt = "Parar filme",
global_window_close_alt = "Fechar janela",
ingame_scroll_up = "Rolar p/ cima",
ingame_scroll_down = "Rolar p/ baixo",
ingame_scroll_left = "Rolar p/ esq.",
ingame_scroll_right = "Rolar p/ dir.",
ingame_scroll_shift = "Veloc. aceler.",
ingame_zoom_in = "Aproximar",
ingame_zoom_in_more = "Aproximar mais",
ingame_zoom_out = "Afastar",
ingame_zoom_out_more = "Afastar mais",
ingame_showmenubar = "Barra de menu",
ingame_showCheatWindow = "Menu de trapaça",
ingame_loadMenu = "Carregar jogo",
ingame_saveMenu = "Salvar jogo",
ingame_jukebox = "Máq. de discos",
ingame_openFirstMessage = "Nív. mensagem",
ingame_pause = "Pausa",
ingame_gamespeed_slowest = "Muito lento",
ingame_gamespeed_slower = "Lento",
ingame_gamespeed_normal = "Normal",
ingame_gamespeed_max = "Rápido",
ingame_gamespeed_thensome = "Muito rápido",
ingame_gamespeed_speedup = "Volume +",
ingame_panel_bankManager = "Ger. do banco",
ingame_panel_bankStats = "Estado da conta",
ingame_panel_staffManage = "Lista de funcion.",
ingame_panel_townMap = "Mapa da cidade",
ingame_panel_casebook = "Ficha clinica",
ingame_panel_research = "Pesquisa",
ingame_panel_status = "Estado",
ingame_panel_charts = "Gráfico",
ingame_panel_policy = "Normas",
ingame_panel_map_alt = "Mapa da cidade 2",
ingame_panel_research_alt = "Pesquisa 2",
ingame_panel_casebook_alt = "Ficha clinica 2",
ingame_panel_casebook_alt02 = "Ficha clinica 3",
ingame_panel_buildRoom = "Construir sala",
ingame_panel_furnishCorridor = "Mobiliar corredor",
ingame_panel_editRoom = "Editar sala",
ingame_panel_hireStaff = "Contratar",
ingame_rotateobject = "Girar objeto",
ingame_quickSave = "Salvar rapid.",
ingame_quickLoad = "Carregar rapid.",
ingame_restartLevel = "Reiniciar nível",
ingame_quitLevel = "Sair do nível",
ingame_setTransparent = "Transparente",
ingame_toggleAnnouncements = "Anúncios",
ingame_toggleSounds = "Sons",
ingame_toggleMusic = "Música",
ingame_toggleAdvisor = "Conselheiro",
ingame_toggleInfo = "Informaçäo",
ingame_poopLog = "Extrair relatório",
ingame_poopStrings = "Extrair textos",
ingame_patient_gohome = "Enviar p/ casa",
ingame_storePosition_1 = "1",
ingame_storePosition_2 = "2",
ingame_storePosition_3 = "3",
ingame_storePosition_4 = "4",
ingame_storePosition_5 = "5",
ingame_storePosition_6 = "6",
ingame_storePosition_7 = "7",
ingame_storePosition_8 = "8",
ingame_storePosition_9 = "9",
ingame_storePosition_0 = "10",
ingame_recallPosition_1 = "1",
ingame_recallPosition_2 = "2",
ingame_recallPosition_3 = "3",
ingame_recallPosition_4 = "4",
ingame_recallPosition_5 = "5",
ingame_recallPosition_6 = "6",
ingame_recallPosition_7 = "7",
ingame_recallPosition_8 = "8",
ingame_recallPosition_9 = "9",
ingame_recallPosition_0 = "10",
}
tooltip.hotkey_window = {
button_accept = "Aceitar e salvar atribuiçöes de teclas de atalho",
button_defaults = "Restaura todas as teclas de atalho para os padröes do programa",
button_cancel = "Cancela a atribuiçäo e volta ao menu de opçöes",
caption_panels = "Abre a janela para atribuir teclas do painel",
button_recallPosKeys = "Abre a janela para definir teclas para armazenamento e retorno da posiçöes da câmera",
button_back_02 = "Volta para a janela principal de teclas de atalho. As teclas de atalho alteradas nesta janela podem ser aceitas lá",
}
font_location_window = {
caption = "Selecionar fonte (%1%)",
}
@@ -464,7 +638,7 @@ handyman_window = {
}
tooltip.handyman_window = {
parcel_select = "Parcela onde o faz-tudo aceita tarefas, clique para alterar o configuraçäo"
parcel_select = "Parcela onde o zelador aceita tarefas, clique para alterar o configuraçäo"
}
new_game_window = {
@@ -553,7 +727,7 @@ tooltip.information = {
totd_window = {
tips = {
"Todo hospital precisa de uma Recepçäo e um Consultório Geral para começar. Depois disso, dependerá do tipo de pacientes que visitará seu hospital. Entretanto ter uma farmácia é sempre uma boa ideia.",
"Máquinas como o Inflador precisam de constante manutençäo. Contrate um ou dois Faz-tudo para reparos nestas máquinas, ou colocará seus funcionários e pacientes em risco.",
"Máquinas como o Inflador precisam de constante manutençäo. Contrate um ou dois Zeladores para reparos nestas máquinas, ou colocará seus funcionários e pacientes em risco.",
"Depois de um certo período de trabalho, seus funcionários ficaräo cansados. Certifique-se de construir uma Sala de Descanso, para que posam relaxar.",
"Instale Radiadores suficientes para manter seus funcionários e pacientes aquecidos, do contrário ficaräo infelizes. Use o Mapa da Cidade para localizar pontos do hospital que precisem ser aquecidos.",
"O nível de habilidade de um médico reflete na qualidade e velocidade dos diagnósticos. Coloque um médico experiente no Consultório Geral, assim näo precisará de muitas salas de diagnósticos adicionais.",
@@ -703,6 +877,35 @@ map_editor_window = {
}
}
fax = {
vip_visit_result = {
ordered_remarks = {
[1] = "Que hospital eficaz! A próxima vez que estiver gravemente doente, eu venho aqui.",
[2] = "É isto o que eu chamo de um hospital.",
[3] = "É um hospital excelente. E sei o que digo; visito muitos hospitais.",
[4] = "Que hospital bem administrado! Obrigado pelo seu convite.",
[5] = "Mmmm. Certamente näo é uma má instituiçäo médica.",
[6] = "Gostei do seu estupendo hospital. Agora, alguém quer um curry da Casa do Curry?",
[7] = "Bom, já vi piores. Mas deveria fazer algumas melhoras.",
[8] = "Oh céus! Näo é um lugar agradável para ir se estiver doente.",
[9] = "Vou ser sincero, é um hospital normal. Francamente, eu esperava mais.",
[10] = "Por que me zanguei? Foi pior que ir ver uma ópera de quatro horas!",
[11] = "Estou aborrecido pelo que vi. Chama a isto um hospital? Mais parece uma pocilga!",
[12] = "Estou farto de atrair a atençäo pública e de visitar buracos fedorentos como este! Eu me demito.",
[13] = "Que lixo. Vou tentar fechá-lo.",
[14] = "Eu nunca vi um hospital täo ruim. Que vergonha!",
[15] = "Estou chocado. Você näo pode chamar isso de hospital! Näo me espere ver novamente.",
}
}
}
hotkeys_file_err = {
file_err_01 = "Näo foi possível carregar o arquivo hotkeys.txt. Certifique-se de que o " ..
"Theme Hospital tem permissäo de leitura/escrita ",
file_err_02 = ", ou use a opçäo linha de comando --hotkeys-file=NomeDoArquivo para especificar um arquivo gravável. " ..
"Para referência, o erro ao carregar o arquivo de teclas de atalho foi: ",
}
-------------------------------- UNUSED -----------------------------------
------------------- (kept for backwards compatibility) ----------------------
@@ -712,18 +915,18 @@ tooltip.options_window.change_resolution = "Altera a resoluçäo de janela para
--------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------- MISSING STRINGS IN LANGUAGE "PORTUGUêS DO BRASIL": -----------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------------------
rooms_long.ward = "Enfermaria"
rooms_long.ward = "Sala de Enfermaria"
rooms_long.blood_machine = "Sala de Transfusöes"
rooms_long.emergency = "Sala de Emergências"
rooms_long.general = "Sala de Clínica Geral"
rooms_long.cardiogram = "Sala do Cardiograma"
rooms_long.general = "Diagnóstico Geral"
rooms_long.cardiogram = "Sala de Cardiograma"
rooms_long.decontamination = "Sala de Descontaminaçäo"
rooms_long.jelly_vat = "Banho Gelatinoso"
rooms_long.staffroom = "Sala de Descanso"
rooms_long.hair_restoration = "Sala de Peloterapia"
rooms_long.inflation = "Sala de Inflatoterapia"
rooms_long.operating_theatre = "Sala de Operaçöes"
rooms_long.gps_office = "Consultório Geral"
rooms_long.operating_theatre = "Sala de Cirurgia"
rooms_long.gps_office = "Consultório de Clínica Geral"
rooms_long.training_room = "Sala de Formaçäo"
rooms_long.x_ray = "Sala de Raio-X"
rooms_long.tongue_clinic = "Sala de Laringologia"
@@ -788,7 +991,7 @@ fax.vip_visit_result.remarks.very_bad[3] = "Estou horrorizado. Chama a isto de h
fax.vip_visit_result.remarks.bad[1] = "Por que me zanguei? Foi pior que ir ver uma ópera de quatro horas!"
fax.vip_visit_result.remarks.bad[2] = "Estou aborrecido pelo que vi. Chama a isto um hospital? Mais parece uma pocilga!"
fax.vip_visit_result.remarks.bad[3] = "Estou farto de atrair a atençäo pública e de visitar buracos fedorentos como este! Demito-me."
fax.vip_visit_result.remarks.good[1] = "Que hospital bem dirigido! Obrigado pelo seu convite."
fax.vip_visit_result.remarks.good[1] = "Que hospital bem administrado! Obrigado pelo seu convite."
fax.vip_visit_result.remarks.good[2] = "Mmmm. Certamente näo é uma má instituiçäo médica."
fax.vip_visit_result.remarks.good[3] = "Gostei do seu estupendo hospital. Agora, alguém quer um curry da Casa do Curry?"
fax.vip_visit_result.cash_grant = "Você ganhou um prêmio em dinheiro de $%d."
@@ -1133,7 +1336,7 @@ build_room_window.pick_room_type = "Escolha a sala"
build_room_window.pick_department = "Departamento"
build_room_window.cost = "Custo: "
object.bench = "Banco"
object.pharmacy_cabinet = "Primeiros socorros"
object.pharmacy_cabinet = "Armário de remédios"
object.console = "Console"
object.atom_analyser = "Analisador atômico"
object.fire_extinguisher = "Extintor"
@@ -1141,7 +1344,7 @@ object.scanner = "Scanner"
object.bin = "Cesto de papéis"
object.swing_door1 = "Porta de batente"
object.pool_table = "Mesa de bilhar"
object.screen = "Tela"
object.screen = "Biombo"
object.cabinet = "Arquivo"
object.sofa = "Sofá"
object.projector = "Projetor"
@@ -1191,7 +1394,7 @@ object.drinks_machine = "Máq. de bebidas"
object.op_sink2 = "Pia"
object.table1 = "Mesa"
object.entrance_right = "Porta esquerda de entrada"
object.operating_table = "Mesa de operaçöes"
object.operating_table = "Mesa de cirurgia"
object.tv = "Televisäo"
transactions.bank_loan = "Empréstimo bancário"
transactions.buy_object = "Comprou"
@@ -1289,7 +1492,6 @@ staff_descriptions.bad[10] = "Pessoa desleal e rancorosa. Cheia de ódio. "
staff_descriptions.bad[11] = "Näo toma cuidado e provoca acidentes. "
staff_descriptions.bad[12] = "Näo se importa com o trabalho. Perde tempo. "
staff_descriptions.bad[13] = "Pessoa temerária e sem cuidado. "
staff_descriptions.bad[14] = "Pessoa ardilosa, matreira e subversiva. "
staff_descriptions.bad[15] = "Pessoa arrogante e presunçosa. "
staff_descriptions.misc[1] = "Joga golfe. "
staff_descriptions.misc[2] = "Pratica mergulho. "
@@ -1301,7 +1503,6 @@ staff_descriptions.misc[7] = "Coleciona latas de cerveja. "
staff_descriptions.misc[8] = "Gosta de mergulhar no público em shows. "
staff_descriptions.misc[9] = "Gosta de fazer surf. "
staff_descriptions.misc[10] = "Pratica raffiting. "
staff_descriptions.misc[11] = "Destila uísque. "
staff_descriptions.misc[12] = "Especialista em 'faça você mesmo'. "
staff_descriptions.misc[13] = "Gosta do cinema francês. "
staff_descriptions.misc[14] = "Joga muito Theme Park. "
@@ -1437,7 +1638,6 @@ level_names[13] = "Vila Amistosa"
level_names[14] = "Vila Pancadaria"
level_names[15] = "Vila Sepultura"
pay_rise.definite_quit = "Faça o que fizer, näo ficarei. Estou farto deste local."
pay_rise.regular[1] = "Estou esgotado. Preciso de umas boas férias, mais um aumento de %d, se näo quiser deixo este maldito emprego."
pay_rise.regular[2] = "Estou muito cansado. Preciso de umas férias e de um aumento salarial de %d, até um total de %d. E quero já, seu tirano!"
pay_rise.regular[3] = "Vamos! Trabalho como um escravo. Se me der um bônus de %d, ficarei no seu hospital."
pay_rise.regular[4] = "Estou muito descontente. Exijo um aumento de %d, e um salário final de %d, caso contrário, vou embora."
@@ -1448,7 +1648,7 @@ casebook.reputation = "reputaçäo"
casebook.earned_money = "dinheiro ganho"
casebook.cure_desc.no_cure_known = "Näo há cura conhecida."
casebook.cure_desc.hire_doctors = "Você precisa contratar alguns médicos."
casebook.cure_desc.build_ward = "Ainda tem que construir uma enfermaria."
casebook.cure_desc.build_ward = "Ainda tem que construir uma Enfermaria."
casebook.cure_desc.improve_cure = "Melhorar Cura."
casebook.cure_desc.build_room = "Sugiro que construa %s"
casebook.cure_desc.hire_surgeons = "Você precisa contratar cirurgiöes."
@@ -1509,7 +1709,7 @@ diseases.discrete_itching.cause = "Causa - Insetos diminutos com os dentes afiad
diseases.discrete_itching.name = "Coceira leve"
diseases.discrete_itching.symptoms = "Sintomas - Arranhar-se, o que conduz a um inchaço da parte afetada."
diseases.discrete_itching.cure = "Cura - O paciente bebe um xarope farmacêutico para evitar a coceira da pele."
diseases.alien_dna.cause = "Causa - Enfrentara gigantes providos de sangue alienígena inteligente."
diseases.alien_dna.cause = "Causa - Enfrentar gigantes providos de sangue alienígena inteligente."
diseases.alien_dna.name = "DNA alienígena"
diseases.alien_dna.symptoms = "Sintomas - Metamorfose alienígena gradual e desejo de destruir nossas cidades."
diseases.alien_dna.cure = "Cura - O DNA é mecanicamente removido, limpo de elementos alienígenas e substituídos rapidamente."
@@ -1537,8 +1737,8 @@ diseases.sleeping_illness.cure = "Cura - Uma enfermeira administra uma dose elev
diseases.pregnancy.cause = "Causa - Cortes de eletricidade em áreas urbanas."
diseases.pregnancy.name = "Grávida"
diseases.pregnancy.symptoms = "Sintomas - Muita vontade de comer e um mal-estar no estômago."
diseases.pregnancy.cure = "Cura - Extrai-se ao bebé na sala de operaçöes, limpa-se e entrega-se ao paciente."
diseases.general_practice.name = "Clínica Geral"
diseases.pregnancy.cure = "Cura - O bebê é retirado na Sala de Cirurgia, limpo e entregue ao paciente."
diseases.general_practice.name = "Diagnóstico Geral"
diseases.tv_personalities.cause = "Causa - Ver televisäo durante o dia."
diseases.tv_personalities.name = "Personalitis"
diseases.tv_personalities.symptoms = "Sintomas - Ter a ilusäo de apresentar na televisäo um programa de cozinha."
@@ -1604,7 +1804,7 @@ diseases.golf_stones.cure = "Cura - Dois cirurgiöes operam para extrair os cál
diseases.gastric_ejections.cause = "Causa - Comida mexicana ou da India muito condimentada."
diseases.gastric_ejections.name = "Vômito"
diseases.gastric_ejections.symptoms = "Sintomas - O paciente vomita a comida ao meio digerir em qualquer momento."
diseases.gastric_ejections.cure = "Cura - Beber um preparado adstringente especial para parar os vômitos."
diseases.gastric_ejections.cure = "Cura - Beber um adstringente especial para parar os vômitos."
diseases.unexpected_swelling.cause = "Causa - Algo inesperado."
diseases.unexpected_swelling.name = "Inchaço inesperado"
diseases.unexpected_swelling.symptoms = "Sintomas - Inchaço."
@@ -1631,7 +1831,7 @@ diseases.serious_radiation.cure = "Cura - O Paciente deve ir para a Ducha de Des
diseases.iron_lungs.cause = "Causa - A contaminaçäo urbana misturada com restos do Kebab."
diseases.iron_lungs.name = "Pulmäo de aço"
diseases.iron_lungs.symptoms = "Sintomas - Capacidade para aspirar fogo e gritar debaixo da água."
diseases.iron_lungs.cure = "Cura - Dois cirurgiöes realizam uma operaçäo para eliminar as partes duras do pulmäo na sala de operaçöes."
diseases.iron_lungs.cure = "Cura - Dois cirurgiöes realizam uma operaçäo para eliminar as partes duras do pulmäo na Sala de Cirurgia."
diseases.diag_ultrascan.name = "Diag: Ultra Scanner"
diseases.autopsy.name = "Autópsia"
dynamic_info.staff.ability = "Habilidade"
@@ -1734,7 +1934,7 @@ competitor_names[21] = "JOSHUA"
competitor_names[22] = "DANEEL"
competitor_names[23] = "OLIVAW"
competitor_names[24] = "NIC"
staff_class.handyman = "Faz-tudo"
staff_class.handyman = "Zelador"
staff_class.nurse = "Enfermeira"
staff_class.surgeon = "Cirurgiäo"
staff_class.receptionist = "Recepcionista"
@@ -1755,7 +1955,7 @@ menu_debug.lose_game_anim[5] = " PERDER JOGO 5 ANIM "
menu_debug.lose_game_anim[6] = " PERDER JOGO 6 ANIM "
menu_debug.lose_game_anim[7] = " PERDER JOGO 7 ANIM "
menu_debug.show_help_hotspot = " MOSTRAR AJUDAS "
menu_debug.porter_pagers = " PAGINADORES DE FAZ-TUDO "
menu_debug.porter_pagers = " PAGINADORES DE ZELADOR "
menu_debug.mapwho_checking = " VERIFICAR LOCALIZAÇAO "
menu_debug.machine_pagers = " PAGINADORES DE MAQUINA "
menu_debug.keep_clear_cells = " CONSERVAR AS CELAS LIMPAS "
@@ -1790,10 +1990,10 @@ trophy_room.cleanliness.regional_bad[1] = "O seu hospital é o mais sujo da regi
trophy_room.happy_vips.trophies[1] = "Ganhou o Prêmio Nobel de VIP Exemplares. Todos os que visitaram o seu hospital no ano passado o elogiaram muito."
trophy_room.happy_vips.trophies[2] = "A Agência de Pessoas Famosas deseja-lhe conceder o Prêmio à Celebridade por ter agradado a todos os VIP que visitaram o seu hospital."
trophy_room.happy_vips.trophies[3] = "Ganhou a VIAGEM VIP por melhorar a imagem dos trabalhadores aos olhos de todos os que visitaram o seu hospital. Estupendo!"
trophy_room.gen_repairs.awards[1] = "Foi-lhe concedido um prêmio especial pela diligência dos seus faz-tudo na hora de manter as máquinas do seu hospital em bom estado. Muito bem. Tire umas férias."
trophy_room.gen_repairs.awards[2] = "Os seus faz-tudo trabalharam melhor que os de qualquer outro hospital. Este é um importante acontecimento de felicidade."
trophy_room.gen_repairs.awards[3] = "As suas máquinas estäo em um magnífico estado. A dedicaçäo dos seus faz-tudo foi extraordinária. Merecem este prestigioso prêmio. Um trabalho estupendo!"
trophy_room.gen_repairs.penalty[1] = "Os seus faz-tudo näo cuidaram bem das suas máquinas. Deveria fiscalizá-los melhor ou contratar mais para que façam o trabalho melhor."
trophy_room.gen_repairs.awards[1] = "Foi-lhe concedido um prêmio especial pela diligência dos seus zeladores na hora de manter as máquinas do seu hospital em bom estado. Muito bem. Tire umas férias."
trophy_room.gen_repairs.awards[2] = "Os seus zeladores trabalharam melhor que os de qualquer outro hospital. Este é um importante acontecimento de felicidade."
trophy_room.gen_repairs.awards[3] = "As suas máquinas estäo em um magnífico estado. A dedicaçäo dos seus zeladores foi extraordinária. Merecem este prestigioso prêmio. Um trabalho estupendo!"
trophy_room.gen_repairs.penalty[1] = "Os seus zeladores näo cuidaram bem das suas máquinas. Deveria fiscalizá-los melhor ou contratar mais para que façam o trabalho melhor."
trophy_room.gen_repairs.penalty[2] = "Näo fez uma boa manutençäo. Os seus funcionários deveriam cuidar melhor dos seus equipamentos."
trophy_room.many_cured.penalty[1] = "O seu hospital näo cura com eficácia os pacientes que o necessitam. Tenha mais atençäo às curas para que sejam mais eficazes."
trophy_room.many_cured.penalty[2] = "O seu hospital é o menos eficaz na hora de curar pacientes. Decepcionou o Ministério e defraudou a si mesmo. Näo diremos mais."
@@ -1901,9 +2101,9 @@ rooms_short.jelly_vat = "Banho gelatinoso"
rooms_short.staffroom = "Sala de Descanso"
rooms_short.hair_restoration = "Pêloterapia"
rooms_short.inflation = "Inflatoterapia"
rooms_short.operating_theatre = "Sala de Operaçöes"
rooms_short.operating_theatre = "Sala de Cirurgia"
rooms_short.reception = "Recepçäo"
rooms_short.gps_office = "Consultório Geral"
rooms_short.gps_office = "Consultório de CG"
rooms_short.training_room = "Formaçäo"
rooms_short.x_ray = "Raio-X"
rooms_short.tongue_clinic = "Laringologia"
@@ -1941,7 +2141,7 @@ tooltip.objects.swing_door1 = "52 PORTAS DUPLAS 1"
tooltip.objects.pool_table = "Mesa de bilhar: ajuda os seus funcionários a relaxarem."
tooltip.objects.screen = "16 TELAS"
tooltip.objects.cabinet = "Armário: contém fichas dos pacientes, notas e documentos sobre pesquisa."
tooltip.objects.sofa = "Sofá: Os empregados que descansam na sala de descanso sentaräo em um sofá, a menos que tenham uma maneira melhor de relaxar-se."
tooltip.objects.sofa = "Sofá: Os funcionários que descansam na Sala de Descanso sentaräo em um sofá, a menos que tenham uma maneira melhor de relaxar-se."
tooltip.objects.projector = "37 PROJETORES"
tooltip.objects.table2 = "12 MESAS"
tooltip.objects.shower = "54 DUCHAS DESCONTAMINADORAS"
@@ -2002,7 +2202,7 @@ tooltip.buy_objects_window.decrease = "Comprar menos um objeto"
tooltip.buy_objects_window.total_value = "Custo total dos objetos solicitados"
tooltip.machine_window.status = "Estado das máquinas."
tooltip.machine_window.close = "Cancelar caixa de diálogo"
tooltip.machine_window.repair = "Mandar o faz-tudo reparar as máquinas."
tooltip.machine_window.repair = "Mandar o zelador reparar as máquinas."
tooltip.machine_window.name = "Nome"
tooltip.machine_window.times_used = "Número de vezes que reparou as máquinas."
tooltip.machine_window.replace = "Substituir máquinas"
@@ -2034,7 +2234,7 @@ tooltip.research.improvements_dec = "Reduzir a porcentagem nas melhorias"
tooltip.research.drugs_inc = "Aumentar a porcentagem da pesquisa farmacêutica"
tooltip.research.allocated_amount = "Quantidade do orçamento alocado"
tooltip.statement.close = "Fechar tela do estado das contas"
tooltip.rooms.ward = "As enfermarias säo úteis tanto para diagnosticar como para tratamento. Envie aqui os pacientes para observaçäo e para recuperar de uma operaçäo sob o cuidado de uma enfermeira."
tooltip.rooms.ward = "As Enfermarias säo úteis tanto para diagnosticar como para tratamento. Envie aqui os pacientes para observaçäo e para recuperar de uma operaçäo sob o cuidado de uma enfermeira."
tooltip.rooms.blood_machine = "O médico usa o Transfusiômetro para diagnosticar os pacientes"
tooltip.rooms.fracture_clinic = "A enfermeira utiliza a consulta de traumatologia para arrumar fraturas"
tooltip.rooms.cardiogram = "O médico usa a cardiologia para diagnosticar os pacientes"
@@ -2043,16 +2243,16 @@ tooltip.rooms.jelly_vat = "O médico usa o Banho Gelatinoso para curar a gelatin
tooltip.rooms.staffroom = "Os médicos e as enfermeiras descansam na Sala de Descanso"
tooltip.rooms.hair_restoration = "O médico usa o Pêlo-restaurador para curar a calvície"
tooltip.rooms.inflation = "O médico usa a Inflatoterapia para curar a cabeça inflada"
tooltip.rooms.gps_office = "Os pacientes recebem sua primeira consulta na sala de Consultório Geral"
tooltip.rooms.training_room = "A sala de formaçäo com um consultor serve para formar outros médicos"
tooltip.rooms.gps_office = "Os pacientes recebem seu primeiro atendimento no Consultório de Clínica Geral"
tooltip.rooms.training_room = "A Sala de Formaçäo com um consultor serve para formar outros médicos"
tooltip.rooms.x_ray = "O médico usa Raio-X para diagnosticar os pacientes"
tooltip.rooms.general_diag = "O médico utiliza a sala de Diagnóstico Geral para fazer um diagnóstico básico. É barato e frequentemente eficaz"
tooltip.rooms.operating_theatre = "Na sala de operaçöes faltam dois cirurgiöes qualificados"
tooltip.rooms.operating_theatre = "Na Sala de Cirurgia faltam dois cirurgiöes qualificados"
tooltip.rooms.ultrascan = "O médico usa o Ultra-scanner para diagnosticar os pacientes"
tooltip.rooms.psychiatry = "A consulta de psiquiatria cura os pacientes loucos e ajuda a diagnosticar outros pacientes, mas requer um médico qualificado em psiquiatria."
tooltip.rooms.toilets = "Construa banheiros para impedir que os pacientes armem uma confusäo no seu hospital!"
tooltip.rooms.psychiatry = "A Psiquiatria cura os pacientes loucos e ajuda a diagnosticar outros pacientes, mas requer um médico qualificado em psiquiatria."
tooltip.rooms.toilets = "Construa Banheiros para impedir que os pacientes armem uma confusäo no seu hospital!"
tooltip.rooms.dna_fixer = "O médico usa o Reparador de DNA para curar os pacientes com DNA alienígena"
tooltip.rooms.pharmacy = "Na enfermaria, a enfermeira administra remédios para curar os pacientes"
tooltip.rooms.pharmacy = "Na Enfermaria, a enfermeira administra remédios para curar os pacientes"
tooltip.rooms.tongue_clinic = "O médico usa a Laringologia para curar a língua comprida"
tooltip.rooms.research_room = "Os médicos pesquisadores investigam remédios e máquinas novas no Centro de Pesquisa"
tooltip.rooms.scanner = "O médico usa o Scanner para diagnosticar os pacientes"
@@ -2088,7 +2288,7 @@ tooltip.staff_list.nurses = "Ver a lista das enfermeiras contratadas neste hospi
tooltip.staff_list.skills = "Outras qualificaçöes"
tooltip.staff_list.detail = "Cuidar os detalhes"
tooltip.staff_list.doctors = "Ver a lista dos médicos contratados neste hospital"
tooltip.staff_list.handymen = "Ver a lista dos faz-tudo contratados neste hospital"
tooltip.staff_list.handymen = "Ver a lista dos zeladores contratados neste hospital"
tooltip.staff_list.researcher = "Pesquisador qualificado"
tooltip.staff_list.psychiatrist_train = "Formado um %d%% para ser Psiquiatra"
tooltip.place_objects_window.confirm = "Confirmar"
@@ -2115,12 +2315,12 @@ tooltip.casebook.cure_type.surgery = "Esta doença requer cirurgia"
tooltip.casebook.cure_requirement.hire_staff_old = "Você precisa contratar um %s para este tratamento"
tooltip.casebook.cure_requirement.not_possible = "Näo pode administrar este tratamento"
tooltip.casebook.cure_requirement.hire_surgeon = "Você precisa contratar um segundo cirurgiäo para que realize operaçöes"
tooltip.casebook.cure_requirement.build_ward = "Você precisa construir uma enfermaria para administrar este tratamento"
tooltip.casebook.cure_requirement.build_ward = "Você precisa construir uma Enfermaria para administrar este tratamento"
tooltip.casebook.cure_requirement.possible = "Pode administrar este tratamento"
tooltip.casebook.cure_requirement.build_room = "Você precisa construir uma consulta para dar este tratamento"
tooltip.casebook.cure_requirement.hire_surgeons = "Você precisa contratar dois cirurgiöes para as operaçöes"
tooltip.casebook.cure_requirement.research_machine = "Precisa inventar alguma máquina para este tratamento."
tooltip.casebook.cure_requirement.ward_hire_nurse = "Você precisa de uma enfermeira que trabalhe na enfermaria para administrar este tratamento"
tooltip.casebook.cure_requirement.ward_hire_nurse = "Você precisa de uma enfermeira que trabalhe na Enfermaria para administrar este tratamento"
tooltip.patient_window.center_view = "Enfocar a pessoa"
tooltip.patient_window.warmth = "Quanto calor tem a pessoa"
tooltip.patient_window.close = "Cancelar caixa de diálogo"
@@ -2203,10 +2403,10 @@ tooltip.jukebox.rewind = "Rebobinar máquina de discos"
tooltip.jukebox.current_title = "Máquina de discos"
tooltip.jukebox.loop = "Funcionamento contínuo da máquina de discos"
tooltip.build_room_window.cost = "Preço da consulta selecionada"
tooltip.build_room_window.room_classes.treatment = "Selecione consultas de clínica geral"
tooltip.build_room_window.room_classes.facilities = "Selecione consultas especiais"
tooltip.build_room_window.room_classes.treatment = "Selecione consultas de tratamentos gerais"
tooltip.build_room_window.room_classes.facilities = "Selecione instalaçöes especiais"
tooltip.build_room_window.room_classes.diagnosis = "Selecione consultas de diagnóstico"
tooltip.build_room_window.room_classes.clinic = "Selecione consultas de especialidade médica"
tooltip.build_room_window.room_classes.clinic = "Selecione clínicas especializadas"
tooltip.build_room_window.close = "Sair desta caixa de diálogo e voltar para o jogo"
tooltip.hire_staff_window.surgeon = "Cirurgiäo"
tooltip.hire_staff_window.psychiatrist = "Psiquiatra"
@@ -2222,20 +2422,20 @@ tooltip.hire_staff_window.salary = "Salário"
tooltip.hire_staff_window.staff_ability = "Qualificaçäo dos funcionários"
tooltip.hire_staff_window.doctors = "Ver os médicos que pode contratar"
tooltip.hire_staff_window.next_person = "Ver pessoa seguinte"
tooltip.hire_staff_window.handymen = "Ver os faz-tudo que pode contratar"
tooltip.handyman_window.center_view = "Enfocar o faz-tudo"
tooltip.hire_staff_window.handymen = "Ver os zeladores que pode contratar"
tooltip.handyman_window.center_view = "Enfocar o zelador"
tooltip.handyman_window.close = "Cancelar caixa de diálogo"
tooltip.handyman_window.sack = "Despedir"
tooltip.handyman_window.name = "Nome do faz-tudo"
tooltip.handyman_window.name = "Nome do zelador"
tooltip.handyman_window.pick_up = "Apanhar"
tooltip.handyman_window.prio_litter = "Aumentar a prioridade do faz-tudo para a limpeza"
tooltip.handyman_window.prio_litter = "Aumentar a prioridade do zelador para a limpeza"
tooltip.handyman_window.ability = "Qualificaçäo"
tooltip.handyman_window.prio_machines = "Aumentar a prioridade do faz-tudo para reparar as máquinas"
tooltip.handyman_window.prio_machines = "Aumentar a prioridade do zelador para reparar as máquinas"
tooltip.handyman_window.salary = "Salário"
tooltip.handyman_window.face = "Face do faz-tudo"
tooltip.handyman_window.face = "Face do zelador"
tooltip.handyman_window.happiness = "Satisfaçäo"
tooltip.handyman_window.tiredness = "Cansaço"
tooltip.handyman_window.prio_plants = "Aumentar a prioridade do faz-tudo para regar as plantas"
tooltip.handyman_window.prio_plants = "Aumentar a prioridade do zelador para regar as plantas"
tooltip.window_general.cancel = "Cancelar"
tooltip.window_general.confirm = "Confirmar"
adviser.earthquake.ended = "Nossa! Acreditei que ia ser o pior de todos! Alcançou um %d na Escala Richter."
@@ -2245,7 +2445,7 @@ adviser.staff_place_advice.only_psychiatrists = "Os Médicos só podem trabalhar
adviser.staff_place_advice.doctors_cannot_work_in_room = "Os Médicos näo podem trabalhar em %s"
adviser.staff_place_advice.only_researchers = "Os Médicos só podem trabalhar em um Centro de Pesquisa se estiverem qualificados para isso."
adviser.staff_place_advice.nurses_cannot_work_in_room = "As Enfermeiras näo podem trabalhar em %s"
adviser.staff_place_advice.only_surgeons = "Os Médicos só podem trabalhar em uma sala de operaçöes se estiverem qualificados para isso."
adviser.staff_place_advice.only_surgeons = "Os Médicos só podem trabalhar em uma Sala de Cirurgia se estiverem qualificados para isso."
adviser.staff_place_advice.receptionists_only_at_desk = "As Recepcionistas só podem trabalhar na Recepçäo."
adviser.staff_place_advice.only_doctors_in_room = "Apenas Médicos podem trabalhar em %s"
adviser.staff_place_advice.only_nurses_in_room = "Só Enfermeiras podem trabalhar na %s"
@@ -2256,7 +2456,7 @@ adviser.epidemic.hurry_up = "Se näo acabar em seguida com esta epidemia, terá
adviser.epidemic.multiple_epidemies = "Parece que tem mais de uma epidemia de uma vez. Isto poderia ser desastroso, sendo assim faz algo rapidamente."
adviser.epidemic.serious_warning = "Esta doença contagiosa começa a ser grave. Tem que fazer algo brevemente!"
adviser.warnings.patients_unhappy = "Os pacientes näo gostam do seu hospital. Deveria fazer algo para melhorá-lo."
adviser.warnings.too_much_litter = "Há um problema de limpeza. Mais faz-tudo é a soluçäo."
adviser.warnings.too_much_litter = "Há um problema de limpeza. Mais zeladores é a soluçäo."
adviser.warnings.patients_very_thirsty = "Os pacientes têm muita sede. Se näo colocar logo algumas máquinas de bebidas, as pessoas iräo embora do seu hospital para tomar algum refresco."
adviser.warnings.queue_too_long_at_reception = "Tem muitos pacientes à espera de reservas para as consultas na recepçäo. Coloque outra Recepçäo e contrate outra Recepcionista."
adviser.warnings.patients_thirsty = "As pessoas começam a ter sede. Talvez deveria colocar máquinas de bebidas."
@@ -2267,10 +2467,10 @@ adviser.warnings.machines_falling_apart = "Suas máquinas estäo caindo aos peda
adviser.warnings.financial_trouble3 = "O seu saldo tem um aspecto preocupante. Faça algo de efetivo. Está a %d do desastre."
adviser.warnings.nurses_tired2 = "As suas enfermeiras estäo muito cansadas. Faça descansá-las agora mesmo."
adviser.warnings.people_have_to_stand = "Há pacientes de pé. Faça-os sentar já!"
adviser.warnings.handymen_tired = "Os seus faz-tudo estäo muito cansados. Mande-os descansar agora mesmo."
adviser.warnings.change_priorities_to_plants = "Tem que alterar as prioridades dos seus faz-tudo para que empreguem mais tempo regando as suas plantas."
adviser.warnings.handymen_tired = "Os seus zeladores estäo muito cansados. Mande-os descansar agora mesmo."
adviser.warnings.change_priorities_to_plants = "Tem que alterar as prioridades dos seus zeladores para que empreguem mais tempo regando as suas plantas."
adviser.warnings.staff_tired = "Os seus funcionários estäo incrivelmente cansados. Se näo os deixa descansar um momento em uma Sala de Descanso, alguns poderäo cair de esgotamento."
adviser.warnings.some_litter = "Os faz-tudo podem recolher tudo este lixo antes de que se converta em um grave problema."
adviser.warnings.some_litter = "Os zeladores podem recolher todo este lixo antes de que se converta em um grave problema."
adviser.warnings.cash_low_consider_loan = "As suas finanças andam mal. Já pensou em um empréstimo?"
adviser.warnings.reduce_staff_rest_threshold = "Tente alterar o Tempo de Descanso dos funcionários no tela de Normas para que possam descansar mais frequentemente. É só uma sugestäo."
adviser.warnings.build_staffroom = "Construa uma Sala de Descanso o mais rápido possível. Os seus funcionários trabalham muito e estäo à beira do colapso. Vamos, seja razoável!"
@@ -2294,8 +2494,8 @@ adviser.warnings.money_very_low_take_loan = "O seu saldo bancário é muito baix
adviser.warnings.finanical_trouble2 = "Consiga algum crédito ou estará acabado. Perderá o nível se perder outros %d."
adviser.warnings.patients_very_cold = "Os pacientes têm muito frio. Suba o aquecimento ou coloque mais radiadores no seu hospital."
adviser.warnings.deal_with_epidemic_now = "Se näo acabar em seguida com esta epidemia, terá muitos problemas. Despacha-se!"
adviser.warnings.litter_catastrophy = "O problema da falta de limpeza é grave. Coloque vários faz-tudo para trabalhar agora mesmo!"
adviser.warnings.litter_everywhere = "Há sujeira por toda parte. Coloque mais faz-tudo para trabalhar."
adviser.warnings.litter_catastrophy = "O problema da falta de limpeza é grave. Coloque vários zeladores para trabalhar agora mesmo!"
adviser.warnings.litter_everywhere = "Há sujeira por toda parte. Coloque mais zeladores para trabalhar."
adviser.warnings.machinery_slightly_damaged = "A maquinaria do seu hospital está um pouco danificada. Näo se esqueça de as reparar."
adviser.warnings.patients_getting_hot = "Os pacientes têm muito calor. Baixe o aquecimento ou retire alguns radiadores."
adviser.warnings.no_patients_last_month = "No último mês, näo vieram novos pacientes ao seu hospital. Horrível!"
@@ -2319,27 +2519,27 @@ adviser.warnings.more_benches = "Poderia colocar mais bancos. Os doentes protest
adviser.warnings.bankruptcy_imminent = "Ei! Vai ficar sem máquinas. Tome cuidado!"
adviser.warnings.need_toilets = "Os pacientes precisam de banheiros. Coloque-os em um lugar acessível."
adviser.warnings.more_toilets = "Você precisa de mais banheiros. As pessoas näo aguentam mais."
adviser.warnings.plants_dying = "As suas plantas estäo secando. Precisam de água. Consiga mais faz-tudo para que cuidem disso. Os pacientes näo gostam das plantas mortas."
adviser.warnings.plants_dying = "As suas plantas estäo secando. Precisam de água. Consiga mais zeladores para que cuidem disso. Os pacientes näo gostam das plantas mortas."
adviser.warnings.people_did_it_on_the_floor = "Alguns dos seus pacientes näo podem aguentar mais. Alguém vai ter que limpar sem descanso."
adviser.warnings.need_staffroom = "Construa uma sala de descanso para que os seus empregados possam descansar."
adviser.warnings.need_staffroom = "Construa uma Sala de Descanso para que os seus empregados possam descansar."
adviser.warnings.plants_thirsty = "Precisa cuidar das suas plantas. Estäo secando."
adviser.warnings.too_many_plants = "Tem muitas plantas. Isto parece uma selva."
adviser.warnings.receptionists_tired2 = "As suas recepcionistas estäo esgotadas. Deixa que descansem agora mesmo."
adviser.warnings.machine_severely_damaged = "%s está prestes a se quebrar."
adviser.warnings.machinery_very_damaged = "É uma emergência! Faça com que que um faz-tudo repare as sas máquinas em seguida. Estäo a ponto de explodir!"
adviser.warnings.machinery_very_damaged = "É uma emergência! Faça com que um zelador repare as suas máquinas em seguida. Estäo a ponto de explodir!"
adviser.warnings.hospital_is_rubbish = "As pessoas estäo dizendo abertamente que seu hospital é lixo. Já sabe que logo iräo se queixar em outro lugar."
adviser.warnings.handymen_tired2 = "Os seus faz-tudo estäo cansados. Mande-os descansar imediatamente."
adviser.warnings.machinery_damaged2 = "Precisa contratar um faz-tudo para reparar as suas máquinas rapidamente."
adviser.warnings.handymen_tired2 = "Os seus zeladores estäo cansados. Mande-os descansar imediatamente."
adviser.warnings.machinery_damaged2 = "Precisa contratar um zelador para reparar as suas máquinas rapidamente."
adviser.warnings.staff_very_cold = "O pessoal reclama do frio. Suba o aquecimento ou coloque mais radiadores."
adviser.warnings.desperate_need_for_watering = "Necessita desesperadamente contratar um faz-tudo para que cuide das suas plantas."
adviser.warnings.desperate_need_for_watering = "Necessita desesperadamente contratar um zelador para que cuide das suas plantas."
adviser.warnings.staff_unhappy2 = "Os seus funcionários, no geral, estäo descontentes. Logo, pediräo mais dinheiro."
adviser.staff_advice.too_many_nurses = "Está contratando muitas enfermeiras."
adviser.staff_advice.need_handyman_litter = "Os pacientes estäo sujando o seu hospital. Contrate uma faz-tudo para se encarregar da limpeza."
adviser.staff_advice.need_nurses = "Contrate mais enfermeiras. Na enfermaria e na farmácia faltam enfermeiras."
adviser.staff_advice.need_handyman_litter = "Os pacientes estäo sujando o seu hospital. Contrate um zelador para se encarregar da limpeza."
adviser.staff_advice.need_nurses = "Contrate mais enfermeiras. Na Enfermaria e na farmácia faltam enfermeiras."
adviser.staff_advice.need_doctors = "Você precisa de mais médicos. Tente colocar os seus melhores médicos nas consultas com mais pacientes."
adviser.staff_advice.too_many_doctors = "Tem muitos médicos. Alguns estäo desocupados neste momento."
adviser.staff_advice.need_handyman_machines = "Terá que contratar um faz-tudo se quiser que as máquinas do seu hospital funcionem."
adviser.staff_advice.need_handyman_plants = "Contrate um faz-tudo para regar as plantas."
adviser.staff_advice.need_handyman_machines = "Terá que contratar um zelador se quiser que as máquinas do seu hospital funcionem."
adviser.staff_advice.need_handyman_plants = "Contrate um zelador para regar as plantas."
adviser.praise.few_have_to_stand = "Quase ninguém fica de pé no seu hospital. Os seus pacientes estäo contentes."
adviser.praise.patients_cured = "%d pacientes curados."
adviser.praise.plants_are_well = "Muito bem. Está cuidando bem das suas plantas. Estupendo."
@@ -2347,8 +2547,8 @@ adviser.praise.plenty_of_benches = "Há muitos assentos, assim näo há problema
adviser.praise.plants_thriving = "Muito bem. As suas plantas estäo muito viçosas. Têm um aspecto maravilhoso. Mantenha-as assim e poderá ganhar um prêmio por isto."
adviser.praise.many_benches = "Agora os seus pacientes têm suficientes assentos. Muito bem!"
adviser.praise.many_plants = "Fantástico! Tem muitas plantas. Os seus pacientes agradeceräo isso."
adviser.surgery_requirements.need_surgeons_ward_op = "Precisa contratar dois cirurgiöes e construir uma enfermaria e uma sala de operaçöes para realizar operaçöes."
adviser.surgery_requirements.need_surgeon_ward = "Precisa contratar um outro cirurgiäo e construir uma enfermaria para poder realizar operaçöes."
adviser.surgery_requirements.need_surgeons_ward_op = "Precisa contratar dois cirurgiöes e construir uma Enfermaria e uma Sala de Cirurgia para realizar operaçöes."
adviser.surgery_requirements.need_surgeon_ward = "Precisa contratar um outro cirurgiäo e construir uma Enfermaria para poder realizar operaçöes."
adviser.multiplayer.objective_completed = "Completou o objetivo. Felicidades!"
adviser.multiplayer.objective_failed = "Näo conseguiu completar o objetivo."
adviser.multiplayer.everyone_failed = "Todos falharam ao satisfazer aquele último objetivo."
@@ -2364,7 +2564,7 @@ adviser.research.new_available = "Já pode utilizar um novo %s."
adviser.research.machine_improved = "O Centro de Pesquisa trouxe melhorias para %s."
adviser.research.autopsy_discovered_rep_loss = "Foi descoberta uma máquina de autópsias. Aguarde uma reaçäo desfavorável das pessoas."
adviser.information.emergency = "Há uma emergência! Mexa-se! Mexa-se! Mexa-se!"
adviser.information.handyman_adjust = "Pode conseguir que o faz-tudo limpe melhor se estabelecer as suas prioridades."
adviser.information.handyman_adjust = "Pode conseguir com que o zelador limpe melhor se estabelecer as suas prioridades."
adviser.information.promotion_to_specialist = "Um dos seus Médicos foi promovido para %s."
adviser.information.pay_rise = "Um funcionário ameaça se demitir. Pode atender a sua petiçäo ou despedi-lo. Clique no último ícone da esquerda para ver quem deseja demitir."
adviser.information.first_death = "Acabou de matar o seu primeiro paciente. Como se sente?"
@@ -2380,11 +2580,11 @@ adviser.information.patient_leaving_too_expensive = "Um paciente está indo embo
adviser.information.place_windows = "Se colocar janelas, as salas teräo mais luz e os seus empregados estaräo mais contentes."
adviser.information.initial_general_advice.first_patients_thirsty = "No seu hospital as pessoas têm sede. Coloque máquinas de bebidas."
adviser.information.initial_general_advice.epidemic_spreading = "Há uma doença contagiosa no seu hospital. Tente curar as pessoas infectadas antes que elas possam ir embora."
adviser.information.initial_general_advice.research_now_available = "Construiu o seu primeiro centro de pesquisa. Agora pode acessar a tela de pesquisa."
adviser.information.initial_general_advice.research_now_available = "Construiu o seu primeiro centro de pesquisa. Agora você pode acessar a tela de pesquisa."
adviser.information.initial_general_advice.autopsy_available = "Foi inventada uma máquina de autópsias. Com ela, poderá se livrar dos pacientes problemáticos e investigar os seus restos. Tome cuidado, o uso desta máquina suscita muita polêmica."
adviser.information.initial_general_advice.rats_have_arrived = "Os ratos invadiram o seu hospital. Tente eliminá-los com o mouse."
adviser.information.initial_general_advice.surgeon_symbol = "Os médicos especializados em cirurgia levam o símbolo: {"
adviser.information.initial_general_advice.machine_needs_repair = "Há uma máquina que precisa ser reparada. Encontre a máquina e aperte sobre ela. Em seguida, aperte no botäo do faz-tudo."
adviser.information.initial_general_advice.machine_needs_repair = "Há uma máquina que precisa ser reparada. Encontre a máquina e aperte sobre ela. Em seguida, aperte no botäo do zelador."
adviser.information.initial_general_advice.research_symbol = "Os médicos pesquisadores levam o símbolo: }"
adviser.information.initial_general_advice.decrease_heating = "As pessoas têm muito calor no seu hospital. Baixe o aquecimento. Isto é feito na tela de mapa da cidade."
adviser.information.initial_general_advice.taking_your_staff = "Alguém está subornando a sua equipe. Terá que discutir com eles para que fiquem."
@@ -2419,7 +2619,7 @@ adviser.level_progress.cured_enough_patients = "Curou muitos pacientes, mas prec
adviser.level_progress.financial_criteria_met = "Tem satisfeito o critério financeiro deste nível. Agora mantenha o seu saldo acima de %d, enquanto assegura de que o hospital funcione eficazmente."
adviser.level_progress.halfway_lost = "Falta 50%% para perder este nível."
adviser.level_progress.another_patient_killed = "Oh, näo! Matou outro paciente. Agora há um total de %d mortes."
adviser.vomit_wave.started = "Parece que tem um vírus de vômitos no seu hospital. Isto näo teria acontecido se o tivesse mantido limpo. Talvez devesse contratar um faz-tudo."
adviser.vomit_wave.started = "Parece que tem um vírus de vômitos no seu hospital. Isto näo teria acontecido se o tivesse mantido limpo. Talvez devesse contratar um zelador."
adviser.vomit_wave.ended = "Ufa! Parece que o vírus que causou essa epidemia de vômitos está controlado. No futuro, mantenha o seu hospital limpo."
adviser.placement_info.staff_cannot_place = "Näo pode colocar este funcionário aqui. Desculpe."
adviser.placement_info.room_cannot_place_2 = "Näo pode colocar a consulta aqui."
@@ -2472,30 +2672,23 @@ adviser.tutorial.select_diagnosis_rooms = "Clique com o botäo esquerdo no ícon
adviser.tutorial.reception_invalid_position = "A Recepçäo ficou cinzenta porque näo é possível colocar a¡. Mover ou girá-la."
adviser.tutorial.build_pharmacy = "Parabéns! Agora, construa uma farmácia e contrate uma enfermeira para fazer o seu hospital funcionar."
adviser.room_requirements.pharmacy_need_nurse = "Contrate uma enfermeira para a Farmácia."
adviser.room_requirements.op_need_two_surgeons = "Contrate dois cirurgiöes para que operem na sala de operaçöes."
adviser.room_requirements.training_room_need_consultant = "Terá que contratar um especialista para que de conferências nesta sala de formaçäo."
adviser.room_requirements.op_need_two_surgeons = "Contrate dois cirurgiöes para que operem na Sala de Cirurgia."
adviser.room_requirements.training_room_need_consultant = "Terá que contratar um especialista para que de conferências nesta Sala de Formaçäo."
adviser.room_requirements.research_room_need_researcher = "Terá que contratar um médico pesquisador para que utilize o centro de pesquisa."
adviser.room_requirements.op_need_ward = "Precisa construir uma enfermaria para os pacientes que väo ser operados."
adviser.room_requirements.op_need_ward = "Precisa construir uma Enfermaria para os pacientes que väo ser operados."
adviser.room_requirements.reception_need_receptionist = "Precisa contratar uma recepcionista para atender os seus pacientes."
adviser.room_requirements.op_need_another_surgeon = "Precisa contratar um outro cirurgiäo para poder usar o sala de operaçöes."
adviser.room_requirements.gps_office_need_doctor = "Contrate um médico para atender no consultório."
adviser.room_requirements.ward_need_nurse = "Contrate uma enfermeira para que trabalhe na enfermaria."
adviser.room_requirements.op_need_another_surgeon = "Precisa contratar um outro cirurgiäo para poder usar a Sala de Cirurgia."
adviser.room_requirements.gps_office_need_doctor = "Contrate um médico para atender no Consultório de Clínica Geral."
adviser.room_requirements.ward_need_nurse = "Contrate uma enfermeira para que trabalhe na Enfermaria."
adviser.room_requirements.psychiatry_need_psychiatrist = "Agora que construiu uma sala de psiquiatria, precisará de um psiquiatra."
introduction_texts.level15 = "Bem, estes säo os mecanismos básicos para pôr em funcionamento um hospital.//Os seus Médicos väo precisar de toda a ajuda que possam obter para diagnosticar alguns dos pacientes. Pode ajudá-los construindo outros equipamentos de diagnóstico como a sala de Diagnóstico Geral."
introduction_texts.level6 = "Utilize toda a sua capacidade para conseguir um hospital que funcione bem e consiga curar muitos pacientes e que possa tratar qualquer caso que pressentem os doentes.//Está avisado de que o ambiente, aqui, é especialmente propenso a germes e infecçöes. A menos que mantenha uma escrupulosa limpeza na sua instituiçäo, terá que fazer frente a uma série de epidemias entre os pacientes. Procure obter um lucro de $20.000 e fazer o valor do seu hospital superar os $140.000. "
introduction_texts.level3 = "Desta vez colocará o seu hospital em uma área rica.//O Ministério da Saúde espera que consiga curar muitos pacientes. Terá que ganhar uma boa reputaçäo para começar, mas uma vez que o hospital comece a funcionar, concentre-se em ganhar todo o dinheiro que puder. Também pode haver emergências. Produzem-se quando chega muita gente que sofre da mesma doença. Se os curar dentro de um prazo determinado, conseguirá aumentar a sua reputaçäo e ganhar um grande extra. Haverá doenças, como o complexo de rei, e deverá ter orçamento para construir uma sala de operaçöes e junto a ela, uma enfermaria. Tem que ganhar $20.000 para superar este nível."
introduction_texts.level12 = "Agora enfrentará o maior dos desafios. Impressionado com os seus lucros, o Ministério tem uma grande tarefa para você; querem que construa outro magnífico hospital, que tenha um excelente lucro e uma reputaçäo incrível. Também querem que compre todo o terreno que puder, cure tudo (e queremos dizer todas as doenças) e ganhe todos os prêmios. Acha que conseguirá? Ganhe $650.000, cure 750 pessoas e consiga uma reputaçäo de 800 para ganhar este nível. "
introduction_texts.level9 = "Depois de depositar o dinheiro na conta bancária do Ministério e pagar uma nova limousine para o Ministro, agora pode dedicar a criar um bom hospital para cuidar dos doentes e necessitados. Aqui terá um montäo de problemas diferentes. Se os seus funcionários tiverem uma boa formaçäo e suficientes consultas, poderäo resolver qualquer situaçäo. O seu hospital terá que valer $200.000 e precisará ter $400.000 no banco. Se näo o conseguir näo poderá terminar o nível."
introduction_texts.level11 = "Tem a oportunidade de construir o hospital definitivo. Esta é uma área de enorme prestígio e o Ministério quer que este seja o melhor hospital. Esperamos que ganhe muito dinheiro, alcance uma excelente reputaçäo e que se encarregue de todos os casos que sejam apresentados. Este é um trabalho importante. Terá que ser muito hábil para completá-lo. Também deve ter em conta que foram vistos OVNIs na área. Assegure-se de que os seus funcionários estejam preparados para receber algumas visitas inesperadas. O seu hospital terá que alcançar um valor de $240.000, precisará ter $500.000 no banco e uma reputaçäo de 700."
introduction_texts.level3 = "Desta vez colocará o seu hospital em uma área rica.//O Ministério da Saúde espera que consiga curar muitos pacientes. Terá que ganhar uma boa reputaçäo para começar, mas uma vez que o hospital comece a funcionar, concentre-se em ganhar todo o dinheiro que puder. Também pode haver emergências. Produzem-se quando chega muita gente que sofre da mesma doença. Se os curar dentro de um prazo determinado, conseguirá aumentar a sua reputaçäo e ganhar um grande extra. Haverá doenças, como o complexo de rei, e deverá ter orçamento para construir uma Sala de Cirurgia e junto a ela, uma Enfermaria. Tem que ganhar $20.000 para superar este nível."
introduction_texts.level14 = "Ainda tem mais um desafio: um hospital surpresa totalmente imprevisível. Se conseguir ter êxito, será o vencedor dos vencedores. E näo espere que seja fácil, porque é a tarefa mais difícil que confrontará. Boa sorte!"
introduction_texts.level10 = "Além de dever curar todas as doenças que possa haver, o Ministério pede que empregue um pouco de tempo em aumentar a eficácia dos seus remédios.//Houve algumas queixas por parte do D. Salutíssimo, o Cachorro Guardiäo da Saúde, assim deve procurar com que todos os seus remédios sejam extremamente eficazes para ficar bem. Também, assegure-se de que o seu hospital tenha uma reputaçäo irrepreensível. Procure deixar morrer poucos pacientes. Como sugestäo, deveria deixar espaço para um banho gelatinoso. Para ganhar, os seus remédios deveräo ter uma eficácia de, pelo menos, 80%, tem que conseguir uma reputaçäo de 650 e guardar $500.000 no banco."
introduction_texts.level5 = "Este será um hospital concorrido, que tratará casos muito variados.//Os seus médicos acabam de sair da faculdade, por isso é fundamental que construa uma sala de formaçäo para que alcancem o nível de formaçäo necessário. Só tem três especialistas para ensinar os seus funcionários inexperientes, entäo faça com que estejam contentes. Tem que ter em conta que o hospital está localizado em cima da falha geológica de San Android. Sempre há risco de terremoto. Os terremotos provocaräo danos importantes nas suas máquinas e alteraräo o bom funcionamento do seu hospital. Aumente a sua reputaçäo até 400 e consiga ganhos de $50.000 para triunfar. Também, cure 200 pacientes. "
introduction_texts.level1 = "Bem-vindo ao seu primeiro hospital!//Para fazê-lo funcionar, coloque uma Recepçäo, construa um Consultório e contrate uma recepcionista e um médico. Em seguida, espere que cheguem os pacientes. Seria uma boa ideia construir uma consulta de psiquiatria e contratar um psiquiatra. Uma farmácia e uma enfermeira säo fundamentais para curar os seus pacientes. Cuidado com os casos malignos de cabeça inflada; eles säo solucionados na sala de inflatoterapia. Terá que curar 10 pessoas e assegurar de que a sua reputaçäo näo seja inferior a 200."
introduction_texts.level17 = "Um último aviso. Fique de olho na sua Reputaçäo, é o que atrairá pacientes ao seu estabelecimento. Se näo matar muitas pessoas e os mantiver razoavelmente felizes näo deverá ter muitos problemas com este nível!//Agora é com você, boa sorte."
introduction_texts.level5 = "Este será um hospital concorrido, que tratará casos muito variados.//Os seus médicos acabam de sair da faculdade, por isso é fundamental que construa uma Sala de Formaçäo para que alcancem o nível de formaçäo necessário. Só tem três especialistas para ensinar os seus funcionários inexperientes, entäo faça com que estejam contentes. Tem que ter em conta que o hospital está localizado em cima da falha geológica de San Android. Sempre há risco de terremoto. Os terremotos provocaräo danos importantes nas suas máquinas e alteraräo o bom funcionamento do seu hospital. Aumente a sua reputaçäo até 400 e consiga ganhos de $50.000 para triunfar. Também, cure 200 pacientes. "
introduction_texts.level1 = "Bem-vindo ao seu primeiro hospital!//Para fazê-lo funcionar, coloque uma Recepçäo, construa um Consultório de Clínica Geral e contrate uma recepcionista e um médico. Em seguida, espere que cheguem os pacientes. Seria uma boa ideia construir uma Psiquiatria e contratar um psiquiatra. Uma Farmácia e uma enfermeira säo fundamentais para curar os seus pacientes. Cuidado com os casos malignos de cabeça inflada; eles säo solucionados na Sala de Inflatoterapia. Terá que curar 10 pessoas e assegurar de que a sua reputaçäo näo seja inferior a 200."
introduction_texts.level7 = "Aqui, estará sob a estrita vigilância do Ministério da Saúde, entäo faça com que suas contas reflitam excelentes lucros e aumente a sua reputaçäo.//Näo podemos permitir que haja mortes desnecessárias; Näo beneficiam nada o negócio. Certifique-se de que os seus funcionários estejam em plena forma e de ter todos os equipamentos necessários. Obtenha uma reputaçäo de 600, e um saldo bancário de $200.000."
introduction_texts.level4 = "Mantenha seus pacientes felizes e trate-os com a maior eficácia possível, reduzindo ao máximo o número de óbitos.//A sua reputaçäo está em jogo, portanto procure aumentá-la o máximo que puder. Näo se preocupe muito com dinheiro, pois você o ganhará mais rápido à medida em que sua reputaçäo cresce. A partir de agora, você poderá formar os seus médicos ampliando seus conhecimentos, permitindo-os curar os pacientes com enfermidades mais difíceis. Alcance uma reputaçäo acima de 500."
introduction_texts.level18 = ""
introduction_texts.level16 = "Uma vez que tenha diagnosticado algum dos pacientes, precisará construir salas de tratamento e clínicas para curá-los. Pode começar com uma Farmácia, onde precisará de uma Enfermeira que distribua os remédios."
introduction_texts.level13 = "A sua incrível habilidade como diretor do hospital atraiu a atençäo da Divisäo Secreta Especial do Serviço Secreto Especial. Tenho um trabalho especial para você. Há um hospital infestado de ratos que precisa de um exterminador eficiente. Você deve matar todos os ratos que puder antes de que o pessoal de Manutençäo limpe toda a sujeira. Acha que é apto para esta missäo?"
introduction_texts.level8 = "Só depende de você construir o hospital mais rentável e eficiente possível.//As pessoas por aqui, säo bastante ricas, entäo tente arrecadar o máximo possível. Lembre-se que curar as pessoas é uma coisa muito bonita, mas o que PRECISAMOS de verdade é do dinheiro delas. Arranque todo o dinheiro destes doentes. Acumule a enorme quantia de $300.000 para completar este nível. "
humanoid_name_starts[1] = "GOLD"
@@ -2587,23 +2780,23 @@ insurance_companies[11] = "Seguros Sindicato"
insurance_companies.out_of_business = "FECHADO"
room_descriptions.ward[1] = "Enfermaria//"
room_descriptions.ward[2] = "Os pacientes ficam aqui para serem atendidos por uma enfermeira enquanto säo diagnosticados. Permanecem aqui antes de serem operados.//"
room_descriptions.ward[3] = "A enfermaria requer uma enfermeira. "
room_descriptions.ward[3] = "A Enfermaria requer uma enfermeira. "
room_descriptions.blood_machine[1] = "Sala de Transfusöes//"
room_descriptions.blood_machine[2] = "O transfusiômetro é um elemento do equipamento de diagnóstico que revisa as células do sangue do paciente para descobrir do que sofrem.//"
room_descriptions.blood_machine[3] = "A sala de transfusöes requer um médico. Também precisa de manutençäo. "
room_descriptions.fracture_clinic[1] = "Traumatologia//"
room_descriptions.fracture_clinic[2] = "Os pacientes que têm a desgraça de ter fraturas vêm aqui. O removedor de gesso usa poderosos lasers industriais para cortar os gessos mais duros, causando ao paciente só uma pequena dor.//"
room_descriptions.fracture_clinic[3] = "A sala de traumatologia requer uma enfermeira. Também precisa de uma manutençäo muito frequente. "
room_descriptions.cardiogram[1] = "Sala de Cardiologia//"
room_descriptions.cardiogram[2] = "Aqui é diagnosticado e examinado os pacientes antes que voltem para a consulta, onde porá o tratamento.//"
room_descriptions.cardiogram[3] = "A sala de cardiologia requer um médico. Também precisa de manutençäo. "
room_descriptions.cardiogram[1] = "Cardiologia//"
room_descriptions.cardiogram[2] = "Os pacientes säo diagnosticados aqui, antes de retornarem para o Consultório de Clínica Geral para conseguir um tratamento.//"
room_descriptions.cardiogram[3] = "A Cardiologia requer um médico. Também precisa de manutençäo. "
room_descriptions.decontamination[1] = "Descontaminaçäo//"
room_descriptions.decontamination[2] = "Os pacientes que estiveram expostos à radiaçäo säo enviados rapidamente à consulta de descontaminaçäo. Esta consulta dispöe de uma ducha que os purifica de toda a horrível radioatividade e sujeira.//"
room_descriptions.decontamination[3] = "A ducha descontaminadora requer um médico. Também precisa de um faz-tudo que se ocupe da sua manutençäo. "
room_descriptions.decontamination[3] = "A ducha descontaminadora requer um médico. Também precisa de um zelador que se ocupe da sua manutençäo. "
room_descriptions.jelly_vat[1] = "Banho Gelatinoso//"
room_descriptions.jelly_vat[2] = "Os pacientes que sofrem da risível doença da gelatinitis devem ir a cambalear-se para a consulta de gelatinitis e ser inundados no banheiro gelatinoso. Isto faz com que se curem de um modo que näo resulta de tudo compreensível para a profissäo médica.//"
room_descriptions.jelly_vat[3] = "O banho gelatinoso requer um médico. Também precisa de uma faz-tudo para a sua manutençäo. "
room_descriptions.gp[1] = "Sala de Consultório Geral//"
room_descriptions.jelly_vat[3] = "O banho gelatinoso requer um médico. Também precisa de um zelador para a sua manutençäo. "
room_descriptions.gp[1] = "Consultório de Clínica Geral//"
room_descriptions.gp[2] = "Esta é a consulta mais importante do seu hospital. Os novos pacientes säo enviados aqui para averiguar o que é que eles têm. Entäo, ou fazem outro diagnóstico ou mandam para uma consulta onde possam ser curados. Possivelmente queira construir outro consultório se o primeiro tiver muito trabalho. Quanto maior for o consultório e quanto mais objetos colocar nele, maior prestígio terá o médico. O mesmo acontece com todas as consultas abertas.//"
room_descriptions.gp[3] = "O consultório geral requer um médico. "
room_descriptions.hair_restoration[1] = "Peloterapia//"
@@ -2611,29 +2804,29 @@ room_descriptions.hair_restoration[2] = "Os pacientes que sofrem de calvície se
room_descriptions.hair_restoration[3] = "A consulta de peloterapia requer um médico. Também precisa de manutençäo periódica. "
room_descriptions.inflation[1] = "Inflatoterapia//"
room_descriptions.inflation[2] = "Os pacientes que sofrem a dolorosa doença de terem a cabeça inflada devem ir à sala de inflatoterapia, será desinflada sua cabeça e em seguida vota a ser inflada à pressäo correta.//"
room_descriptions.inflation[3] = "A Inflatoterapia requer um médico. Também precisa de um faz-tudo que se ocupe do seu bom funcionamento. "
room_descriptions.psych[1] = "Sala de Psiquiatria//"
room_descriptions.inflation[3] = "A Inflatoterapia requer um médico. Também precisa de um zelador que se ocupe do seu bom funcionamento. "
room_descriptions.psych[1] = "Psiquiatria//"
room_descriptions.psych[2] = "Os pacientes que säo diagnosticados com uma doença psiquiátrica têm que ir a uma consulta de psiquiatria para receber o tratamento. Os psiquiatras também podem realizar diagnósticos para saber que tipo de doença que sofrem os pacientes, e se estas säo do tipo mental, podem tratá-los deitando-os no divä.//"
room_descriptions.psych[3] = "A sala de psiquiatria requer um médico especializado em psiquiatria. "
room_descriptions.operating_theatre[1] = "Sala de Operaçöes//"
room_descriptions.operating_theatre[2] = "Esta importante instalaçäo serve para curar um grande número de doenças. A sala de operaçöes tem que ser espaçosa e deve estar bem equipada. É uma parte essencial do seu hospital.//"
room_descriptions.operating_theatre[3] = "A sala de operaçöes requer dois médicos especializados em cirurgia. "
room_descriptions.operating_theatre[1] = "Sala de Cirurgia//"
room_descriptions.operating_theatre[2] = "Esta importante instalaçäo serve para curar um grande número de doenças. A Sala de Cirurgia tem que ser espaçosa e deve estar bem equipada. É uma parte essencial do seu hospital.//"
room_descriptions.operating_theatre[3] = "A Sala de Cirurgia requer dois médicos especializados em cirurgia. "
room_descriptions.no_room[1] = ""
room_descriptions.x_ray[1] = "Sala de Raio X//"
room_descriptions.x_ray[2] = "O Raio X fotografa o interior do paciente empregando uma radiaçäo especial para ajudar a equipe a descobrir o que acontece.//"
room_descriptions.x_ray[3] = "A sala de Raio X requer um médico. Também precisa de manutençäo. "
room_descriptions.x_ray[1] = "Sala de Raio-X//"
room_descriptions.x_ray[2] = "O Raio-X fotografa o interior do paciente empregando uma radiaçäo especial para ajudar a equipe a descobrir o que acontece.//"
room_descriptions.x_ray[3] = "A sala de Raio-X requer um médico. Também precisa de manutençäo. "
room_descriptions.tv_room[1] = "TV ROOM NOT USED"
room_descriptions.staff_room[1] = "Sala de Descanso//"
room_descriptions.staff_room[2] = "Os seus funcionários se cansam quando realizam o seu trabalho. Precisam desta sala para descansar e se refrescar. Os funcionários cansados säo mais lentos, pedem mais dinheiro e por fim väo embora. Também cometem mais erros. Vale a pena construir uma sala que tenham muitos passatempos. Assegure-se de que há lugar para vários funcionários ao mesmo tempo. "
room_descriptions.slack_tongue[1] = "Laringologia//"
room_descriptions.slack_tongue[2] = "Os pacientes que sejam diagnosticados com língua comprida na consulta seräo enviados aqui para receber tratamento. O médico utilizará uma máquina de alta tecnologia para estirar a língua e cortá-la e, com o que o paciente voltará a estar säo.//"
room_descriptions.slack_tongue[2] = "Os pacientes que sejam diagnosticados com língua comprida no Consultório de Clínica Geral, seräo enviados aqui para receber tratamento. O médico utilizará uma máquina de alta tecnologia para esticar a língua e cortá-la, assim o paciente ficará curado.//"
room_descriptions.slack_tongue[3] = "A sala de laringologia requer um médico. Também precisa de uma manutençäo muito frequente. "
room_descriptions.dna_fixer[1] = "Genética//"
room_descriptions.dna_fixer[2] = "Os pacientes que foram manipulados por alienígenas, devem ter seu DNA substituído nesta sala. O reparador de DNA é uma máquina complexa e seria sensato ter um extintor na sala, só por garantia.//"
room_descriptions.dna_fixer[3] = "O reparador de DNA requer que um faz-tudo o revise de vez em quando. Também requer um médico especializado em pesquisa para poder usá-lo. "
room_descriptions.dna_fixer[3] = "O reparador de DNA requer que um zelador o revise de vez em quando. Também requer um médico especializado em pesquisa para poder usá-lo. "
room_descriptions.training[1] = "Sala de Formaçäo//"
room_descriptions.training[2] = "Os seus residentes e médicos podem obter uma valiosa qualificaçäo extra aprendendo nesta sala. Um especialista em cirurgia, pesquisa ou psiquiatria ensinará a sua especialidade aos médicos que recebem formaçäo. Os médicos que já possuem estas especialidades veräo a sua habilidade aumentar enquanto estäo aqui.//"
room_descriptions.training[3] = "A sala de formaçäo requer um especialista. "
room_descriptions.training[3] = "A Sala de Formaçäo requer um especialista. "
room_descriptions.toilets[1] = "Banheiro//"
room_descriptions.toilets[2] = "Os pacientes estäo sentindo um chamado da natureza e precisam se aliviar no conforto de suas instalaçöes de banheiro. Pode construir mais banheiros e pias se esperar muitos visitantes. Em alguns casos, terá que pôr mais banheiros em outras partes do hospital. "
room_descriptions.ultrascan[1] = "Ultra Scanner//"
@@ -2645,15 +2838,15 @@ room_descriptions.pharmacy[3] = "A farmácia requer uma enfermeira. "
room_descriptions.research[1] = "Centro de Pesquisa//"
room_descriptions.research[2] = "No centro de pesquisa säo inventados ou melhoram novos remédios e curas. É uma parte essencial do seu hospital, e fará maravilhas para elevar a sua porcentagem de curas.//"
room_descriptions.research[3] = "O Centro de Pesquisa requer um médico especializado em pesquisa. "
room_descriptions.general_diag[1] = "Sala de Diagnóstico Geral//"
room_descriptions.general_diag[1] = "Diagnóstico Geral//"
room_descriptions.general_diag[2] = "Aqui säo examinados os pacientes que precisam de outro diagnóstico. Se em uma consulta näo descobrir os seus problemas, o diagnosticador geral normalmente consegue. Em seguida, volta a enviar os pacientes para a consulta para analisar os resultados obtidos aqui.//"
room_descriptions.general_diag[3] = "A sala de diagnóstico geral requer um médico. "
room_descriptions.general_diag[3] = "A sala de Diagnóstico Geral requer um médico. "
room_descriptions.scanner[1] = "Scanner//"
room_descriptions.scanner[2] = "É diagnosticado com precisäo os pacientes empregando um sofisticado scanner. Em seguida, väo para a consulta para que o médico veja o tratamento posterior.//"
room_descriptions.scanner[2] = "É diagnosticado com precisäo os pacientes empregando um sofisticado scanner. Em seguida, väo para o Consultório de Clínica Geral para que o médico veja o tratamento posterior.//"
room_descriptions.scanner[3] = "O scanner requer um médico. Também precisa de manutençäo. "
room_descriptions.electrolysis[1] = "Eletrólise//"
room_descriptions.electrolysis[2] = "Os pacientes com peludismo säo enviados para esta consulta, onde uma máquina especial de eletrólise elimina os pelos e fecha os poros com eletricidade empregando um composto parecido ao cal.//"
room_descriptions.electrolysis[3] = "A sala de eletrólise requer um médico. Também precisa que um faz-tudo faça a sua manutençäo. "
room_descriptions.electrolysis[3] = "A sala de eletrólise requer um médico. Também precisa que um zelador faça a sua manutençäo. "
staff_list.skill = "NIVEL"
staff_list.morale = "MORAL"
staff_list.tiredness = "CANSAÇO"
@@ -2666,4 +2859,3 @@ menu_file_load[5] = " JOGO 5 "
menu_file_load[6] = " JOGO 6 "
menu_file_load[7] = " JOGO 7 "
menu_file_load[8] = " JOGO 8 "

View File

@@ -18,7 +18,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. --]]
Language("English", "en", "eng")
Language("English", "English", "en", "eng")
Inherit("original_strings", 0)
--Note: All strings should use a single space after full-stops. Only exception is level descriptions.
@@ -59,16 +59,16 @@ vip_names = {
adviser.research.drug_improved_1 = "%s drug has been improved by your Research Department."
-- Disease overrides where there are typos
golf_stones.cure = "Cure - These must be removed by an operation requiring two Surgeons."
ruptured_nodules.cure = "Cure - Two qualified Surgeons must remove the nodules using steady hands."
slack_tongue.cause = "Cause - Chronic over-discussion of soap operas."
slack_tongue.cure = "Cure - The tongue is placed in the Slicer Machine and removed quickly, efficiently, and painfully."
the_squits.cure = "Cure - A glutinous mix of stringy pharmaceutical chemicals solidify the patient's innards."
bloaty_head.cure = "Cure - The swollen head is popped, then re-inflated to the correct PSI using a clever machine."
diseases.golf_stones.cure = "Cure - These must be removed by an operation requiring two Surgeons."
diseases.ruptured_nodules.cure = "Cure - Two qualified Surgeons must remove the nodules using steady hands."
diseases.slack_tongue.cause = "Cause - Chronic over-discussion of soap operas."
diseases.slack_tongue.cure = "Cure - The tongue is placed in the Slicer Machine and removed quickly, efficiently, and painfully."
diseases.the_squits.cure = "Cure - A glutinous mix of stringy pharmaceutical chemicals solidify the patient's innards."
diseases.bloaty_head.cure = "Cure - The swollen head is popped, then re-inflated to the correct PSI using a clever machine."
-- Rooms overrides where there are typos
inflation[2] = "Patients with the painful-yet-humorous condition of Bloaty Head must come to the Inflation Clinic, where the overlarge cranium will be popped and instantly re-inflated to the correct pressure.//"
staff_room[2] = "Your staff get tired as they carry out their jobs. They require this room to relax and refresh themselves. Tired staff slow down, demand more money, and will eventually quit. They also make more mistakes. Building a staff room with plenty for them to do is very worthwhile. Make sure there is room for several staff members at one time. "
room_descriptions.inflation[2] = "Patients with the painful-yet-humorous condition of Bloaty Head must come to the Inflation Clinic, where the overlarge cranium will be popped and instantly re-inflated to the correct pressure.//"
room_descriptions.staff_room[2] = "Your staff get tired as they carry out their jobs. They require this room to relax and refresh themselves. Tired staff slow down, demand more money, and will eventually quit. They also make more mistakes. Building a staff room with plenty for them to do is very worthwhile. Make sure there is room for several staff members at one time. "
-- Staff description overrides where there are typos
staff_descriptions.bad[14] = "Sly, cunning, and subversive. "
@@ -104,8 +104,13 @@ multiplayer.everyone_failed = "Everyone failed to satisfy that last objective. S
-- Override for a disease patient choice typo
disease_discovered_patient_choice.need_to_employ = "Employ a %s to be able to handle this situation."
--Win message override typo
letter[12][2] = "Your successful career as the best hospital administrator since Moses is nearing an end. However, such has been your impact on the cosy world of medicine, the Ministry would like to offer you a salary of $%d simply to appear on our behalf, opening fetes, launching ships, and doing chat shows. The whole world is clamouring for you, and it would be great PR for us all!//"
-- Override for shorter messages and a typo in 12.2
letter[9][2] = "You have proved yourself to be the best hospital administrator in medicine's long and chequered history. Such a momentous achievement cannot go unrewarded, so we would like to offer you the honorary post of Supreme Chief of All Hospitals. This comes with a salary of $%d. You will be given a tickertape parade, and people will show their appreciation wherever you go.//"
letter[10][2] = "Congratulations on successfully running every hospital we assigned you to. Such a superb performance qualifies you for the freedom of all the world's cities. You are to be given a pension of $%d, and all we ask is that you travel, for free, around the nation, promoting the work of all hospitals to your adoring public.//"
letter[11][2] = "Your career has been exemplary, and you are an inspiration to all of us. Thank you for running so many hospitals so well. We would like to grant you a lifetime salary of $%d, and would ask simply that you travel by official open-topped car from city to city, giving lectures about how you achieved so much so fast.//"
letter[11][3] = "You are an example to every wise person, and without exception, everybody in the world regards you as a supreme asset.//"
letter[12][2] = "Your successful career as the best hospital administrator since Moses is nearing an end. Befitting your impact on the nation, the Ministry would like to offer you a salary of $%d simply to appear on our behalf, opening fetes, launching ships, and doing chat shows. It would be great PR for us all!//"
------------------------------- NEW STRINGS -------------------------------
date_format = {
daymonth = "%1% %2:months%",
@@ -187,6 +192,7 @@ menu_debug = {
transparent_walls = " (%1%) TRANSPARENT WALLS ",
limit_camera = " LIMIT CAMERA ",
disable_salary_raise = " DISABLE SALARY RAISE ",
allow_blocking_off_areas = " ALLOW BLOCKING OFF AREAS ",
make_debug_fax = " MAKE DEBUG FAX ",
make_debug_patient = " MAKE DEBUG PATIENT ",
cheats = " (%1%) CHEATS ",
@@ -229,6 +235,7 @@ adviser = {
no_desk_5 = "Well it's about time, you should start to see some patients arriving soon!",
no_desk_6 = "You have a receptionist, so how about building a reception desk for her to work from?",
no_desk_7 = "You've built the reception desk, so how about hiring a receptionist? You won't see any patients until you get this sorted out you know!",
another_desk = "You'll need to build another desk for that new receptionist.",
cannot_afford = "You don't have enough money in the bank to hire that person!", -- I can't see anything like this in the original strings
cannot_afford_2 = "You don't have enough money in the bank to make that purchase!",
falling_1 = "Hey! That is not funny, watch where you click that mouse; someone could get hurt!",
@@ -251,8 +258,8 @@ adviser = {
},
cheats = {
th_cheat = "Congratulations, you have unlocked cheats!",
roujin_on_cheat = "Roujin's challenge activated! Good luck...",
roujin_off_cheat = "Roujin's challenge deactivated.",
roujin_on_cheat = "Roujin's challenge activated! Good luck in the coming months...",
roujin_off_cheat = "Roujin's challenge deactivated. Everything will be back to normal soon.",
},
}
@@ -304,6 +311,7 @@ install = {
misc.not_yet_implemented = "(not yet implemented)"
misc.no_heliport = "Either no diseases have been discovered yet, or there is no heliport on this map. It might be that you need to build a reception desk and hire a receptionist"
misc.cant_treat_emergency = "Your hospital cannot treat this emergency because its disease has not been discovered. Feel free to try again."
main_menu = {
new_game = "Campaign",
@@ -459,6 +467,7 @@ customise_window = {
aliens = "Alien Patients",
fractured_bones = "Fractured Bones",
average_contents = "Average Contents",
remove_destroyed_rooms = "Remove destroyed rooms",
}
tooltip.customise_window = {
@@ -469,6 +478,7 @@ tooltip.customise_window = {
aliens = "Because of the lack of proper animations we have by default made patients with Alien DNA only come from an emergency. To allow patients with Alien DNA to visit your hospital, other than by an emergency, turn this off",
fractured_bones = "Because of a poor animation we have by default made it so there are no female patients with Fractured Bones. To allow female patients with Fractured Bones to visit your hospital, turn this off",
average_contents = "If you would like the game to remember what extra objects you usually add when you build rooms, then turn this option on",
remove_destroyed_rooms = "If you would like to be able to remove destroyed rooms, for a fee, turn this option on",
back = "Close this menu and go back to the Settings Menu",
}
@@ -678,6 +688,7 @@ errors = {
dialog_missing_graphics = "Sorry, the demo data files don't contain this dialog.",
save_prefix = "Error while saving game: ",
load_prefix = "Error while loading game: ",
compatibility_error = "Sorry, this save was created with a newer version of CorsixTH and is not compatible. Please update to a more recent version.",
no_games_to_contine = "There are no saved games.",
load_quick_save = "Error, cannot load the quicksave as it does not exist, not to worry as we have now created one for you!",
map_file_missing = "Could not find the map file %s for this level!",
@@ -687,17 +698,20 @@ errors = {
fractured_bones = "NOTE: The animation for female patients with Fractured Bones is not perfect",
could_not_load_campaign = "Failed to load the campaign: %s",
could_not_find_first_campaign_level = "Could not find the first level of this campaign: %s",
save_to_tmp = "The file at %s could not be used. The game has been saved to %s. Error: %s",
}
warnings = {
levelfile_variable_is_deprecated = "Notice: The level '%s' contains a deprecated variable definition in the level file." ..
"'%LevelFile' has been renamed to '%MapFile'. Please advise the map creator to update the level.",
newersave = "Warning, you have loaded a save from a newer version of CorsixTH. It is not recommended to continue as crashes may occur. Play at your own risk."
}
confirmation = {
needs_restart = "Changing this setting requires CorsixTH to restart. Any unsaved progress will be lost. Are you sure you want to do this?",
abort_edit_room = "You are currently building or editing a room. If all required objects are placed it will be finished, but otherwise it will be deleted. Continue?",
maximum_screen_size = "The screen size you have entered is greater than 3000 x 2000. Larger resolutions are possible but will require better hardware in order to maintain a playable frame rate. Are you sure you want to continue?",
remove_destroyed_room = "Would you like to remove the room for $%d?",
}
information = {
@@ -874,6 +888,28 @@ map_editor_window = {
}
}
fax = {
vip_visit_result = {
ordered_remarks = {
[1] = "What a storming hospital. When I'm next seriously ill, take me there.",
[2] = "Now that's what I call a hospital.",
[3] = "That's a super hospital. And I should know; I've been in a few.",
[4] = "What a well-run hospital. Thanks for inviting me to it.",
[5] = "Hmm. Not a bad medical establishment, certainly.",
[6] = "I did enjoy your charming hospital. Now, anyone fancy a curry at the Taj?",
[7] = "Well, I've seen worse. But you should make some improvements.",
[8] = "Oh dear. Not a nice place to go if you're feeling peaky.",
[9] = "It's a standard hospital, to be honest. Frankly, I expected more.",
[10] = "Why did I bother? It was worse than going to see a four-hour opera!",
[11] = "I'm disgusted by what I saw. Call that a hospital? Pig-sty, more like!",
[12] = "I'm fed up of being in the public spotlight and visiting smelly holes like this! I resign.",
[13] = "What a dump. I'm going to try and get it closed down.",
[14] = "I have never seen such a dreadful hospital. What a disgrace!",
[15] = "I'm shocked. You can't call that a hospital! I'm off for a pint.",
}
}
}
hotkeys_file_err = {
file_err_01 = "Unable to load hotkeys.txt file. Please ensure that CorsixTH " ..
"has permission to read/write ",
@@ -881,6 +917,7 @@ hotkeys_file_err = {
"For reference, the error loading the hotkeys file was: ",
}
transactions.remove_room = "Build: Remove destroyed room"
-------------------------------- UNUSED -----------------------------------
------------------- (kept for backwards compatibility) ----------------------

File diff suppressed because it is too large Load Diff

View File

@@ -648,8 +648,8 @@ adviser = {
patient_abducted = S[11][111], -- what the heck is this? I never got that far in the original...
patient_leaving_too_expensive = S[11][118],
pay_rise = S[11][ 29], -- TODO only in tutorial / first time?
handyman_adjust = S[11][ 71], -- TODO only in tutorial / first time?
pay_rise = S[11][ 29], -- Once only
handyman_adjust = S[11][ 71], -- Once only
fax_received = S[11][136], -- Once only
vip_arrived = S[21][ 9], -- %s (name of VIP)
@@ -1142,9 +1142,9 @@ trophy_room = {
S[55][ 7],
},
},
all_cured = { -- not implemented
all_cured = {
awards = {
S[27][29], -- for 100% treat rate (does that mean none sent home or killed?)
S[27][29], -- for 100% treat rate (none sent home or killed)
},
trophies = {
S[27][30], -- for 100% cure rate

View File

@@ -18,7 +18,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. --]]
Language("Castellano", "Spanish", "es", "spa", "esp", "sp")
Language("Español", "Spanish", "es", "spa", "esp", "sp")
Inherit("english")
Inherit("original_strings", 4)

View File

@@ -1350,7 +1350,7 @@ adviser = {
hire_doctor = "您需要一個醫生來診斷和治療病人。",
place_receptionist = "移動接待員並將她放到任意位置。她將很聰明地自己走到服務台。",
place_windows = "設置窗戶的方法與設置房門的方法相同。您可以不需要窗戶,但是當您的員工可以從窗戶向外張望時,他們將感到快樂。",
confirm_room = "左鍵單擊閃動￿標就可以開業了,也可以通過點擊交叉按鈕返回上一步。",
confirm_room = "左鍵單擊閃動標就可以開業了,也可以通過點擊交叉按鈕返回上一步。",
rotate_and_place_reception = "點擊滑鼠右鍵可以旋轉桌子,並用滑鼠左鍵將其放在醫院中合適位置。",
build_reception = "你好。首先,您的醫院需要一個服務台,您可以從佈置走廊選單中選取。",
doctor_in_invalid_position = "嘿!您不能將醫生放在那裡。",
@@ -1361,21 +1361,21 @@ adviser = {
choose_doctor = "在選擇醫生之前,應重點考慮其能力。",
information_window = "協助視窗將告訴您剛剛建造的一般診斷室信息。",
build_gps_office = "您必須建造一般診斷室才可以診斷病人。",
select_doctors = "點擊閃動的￿標挑選可被雇用的醫生。",
select_diagnosis_rooms = "點擊閃動的￿標將彈出診斷類房間列表。",
select_receptionists = "使用滑鼠左鍵單擊閃動的￿標來查看當前可選擇的接待員。￿標下方的數字表示共有多少個接待員可供選擇。",
select_doctors = "點擊閃動的標挑選可被雇用的醫生。",
select_diagnosis_rooms = "點擊閃動的標將彈出診斷類房間列表。",
select_receptionists = "使用滑鼠左鍵單擊閃動的標來查看當前可選擇的接待員。標下方的數字表示共有多少個接待員可供選擇。",
order_one_reception = "使用滑鼠左鍵單擊閃動的光條,可以訂購一個服務台。",
choose_receptionist = "判斷哪一個接待員擁有好的能力與合適的工資,再按左鍵單擊閃動的￿標來雇用她。",
prev_receptionist = "左鍵單擊閃動的￿標將可以瀏覽到前一個可供選擇的接待員。",
accept_purchase = "點擊閃動的￿標表示購買。",
choose_receptionist = "判斷哪一個接待員擁有好的能力與合適的工資,再按左鍵單擊閃動的標來雇用她。",
prev_receptionist = "左鍵單擊閃動的標將可以瀏覽到前一個可供選擇的接待員。",
accept_purchase = "點擊閃動的標表示購買。",
place_door = "沿藍圖牆壁移動滑鼠,尋找放置房門的合適位置。",
click_and_drag_to_build = "建造一般診斷室時應先決定具體的尺寸。點擊並按住滑鼠左鍵可以設置房間尺寸。",
room_in_invalid_position = "該藍圖是非法的,紅色區域表示藍圖與其它房間或牆壁重疊。",
place_objects = "右擊可以旋轉房屋中的各種物品,再左擊表示確認。",
room_too_small = "該房間的藍圖為紅色是因為其尺寸太小了。通過拖動使其尺寸增大。",
click_gps_office = "點擊閃動￿標表示選擇一般診斷室。",
click_gps_office = "點擊閃動標表示選擇一般診斷室。",
reception_invalid_position = "如果服務台是灰色的,則表示當前位置是非法的。應嘗試移動或旋轉它。",
next_receptionist = "這是接待員列表中的第一個。左鍵單擊閃動的￿標可以瀏覽下一個可供選用的接待員。",
next_receptionist = "這是接待員列表中的第一個。左鍵單擊閃動的標可以瀏覽下一個可供選用的接待員。",
room_big_enough = "藍圖尺寸已經足夠大了。當您鬆開滑鼠按鍵表示確認。如果需要的話,以後還可以根據需要移動或改變其尺寸。",
object_in_invalid_position = "該物品當前位置非法。請要麼將其放到其它位置,要麼對其進行旋轉。",
door_in_invalid_position = "房門設置位置非法。請嘗試藍圖牆壁上的其它位置。",
@@ -1625,9 +1625,9 @@ adviser = {
vip_arrived = "小心!%s正準備訪問您的醫院保持醫院運轉正常這樣才能使他感到愉快。",
epidemic_health_inspector = "您的醫院中出現傳染病的消息已經到達了衛生署。衛生巡查員很快就要到達,快做準備。",
first_death = "這是您第一次殺死病人。感覺如何?",
pay_rise = "您的一個員工威脅要辭職。選擇是否同意其請求,或將其解雇。點擊屏幕左下方的￿標可以查看威脅要辭職的員工信息。",
pay_rise = "您的一個員工威脅要辭職。選擇是否同意其請求,或將其解雇。點擊屏幕左下方的標可以查看威脅要辭職的員工信息。",
place_windows = "設置窗戶將使房間更加明亮,並振奮員工的精神。",
fax_received = "在屏幕左下角剛剛彈出的￿標表示一些重要事件的相關信息,或某些需要您決定的事情。",
fax_received = "在屏幕左下角剛剛彈出的標表示一些重要事件的相關信息,或某些需要您決定的事情。",
},
build_advice = {
placing_object_blocks_door = "設置該物品可以阻止其他人接近。",

View File

@@ -39,6 +39,10 @@ function Map:Map(app)
self.debug_font = false
self.debug_tick_timer = 1
self:setTemperatureDisplayMethod(app.config.warmth_colors_display_default)
-- Difficulty of the level (string) "easy", "full", "hard".
-- Use map:getDifficulty() to query the value.
self.difficulty = nil
end
local flag_cache = {}
@@ -277,6 +281,14 @@ function Map:load(level, difficulty, level_name, map_file, level_intro, map_edit
return objects
end
--! Get the difficulty of the level. Custom levels and campaign always have medium difficulty.
--!return (int) difficulty of the level, 1=easy, 2=medium, 3=hard.
function Map:getDifficulty()
if self.difficulty == "easy" then return 1 end
if self.difficulty == "hard" then return 3 end
return 2
end
--[[! Sets the plot owner of the given plot number to the given new owner. Makes sure
that any room adjacent to the new plot have walls in all directions after the purchase.
!param plot_number (int) Number of the plot to change owner of. Plot 0 is the outside and

View File

@@ -166,6 +166,8 @@ function Door:checkForDeadlock()
for _, action in ipairs(self.reserved_for.action_queue) do
if action.name == "queue" then
if action.queue ~= self.queue or self.queue[1] ~= self.reserved_for then
self.world:gameLog("Warning: Trying to resolve door deadlock at ("
.. tostring(self.tile_x) .. ", " .. tostring(self.tile_y) .. ")")
self.reserved_for = nil
self:getRoom():tryAdvanceQueue()
end

View File

@@ -36,22 +36,22 @@ class "EntranceDoor" (Object)
---@type EntranceDoor
local EntranceDoor = _G["EntranceDoor"]
function EntranceDoor:EntranceDoor(world, object_type, x, y, direction, etc)
function EntranceDoor:EntranceDoor(hospital, object_type, x, y, direction, etc)
self.is_master = object_type == object
self:Object(world, object_type, x, y, direction, etc)
self:Object(hospital, object_type, x, y, direction, etc)
self.occupant_count = 0
self.is_open = false
-- We need to link the master to the slave but we don't know in which order they will be initialized
if self.is_master then -- The master will check for an adjacent slave
local slave_type = "entrance_left_door"
self.slave = world:getObject(x - 1, y, slave_type) or world:getObject(x, y - 1, slave_type) or nil
self.slave = self.world:getObject(x - 1, y, slave_type) or self.world:getObject(x, y - 1, slave_type) or nil
if self.slave then
self.slave.master = self
end
else -- The slave will check for an adjacent master
local master_type = "entrance_right_door"
self.master = world:getObject(x + 1, y, master_type) or world:getObject(x, y + 1, master_type) or nil
self.master = self.world:getObject(x + 1, y, master_type) or self.world:getObject(x, y + 1, master_type) or nil
if self.master then
self.master.slave = self

View File

@@ -35,14 +35,14 @@ class "SwingDoor" (Door)
---@type SwingDoor
local SwingDoor = _G["SwingDoor"]
function SwingDoor:SwingDoor(world, object_type, x, y, direction, etc)
function SwingDoor:SwingDoor(hospital, object_type, x, y, direction, etc)
self.is_master = object_type == object
self:Door(world, object_type, x, y, direction, etc)
self:Door(hospital, object_type, x, y, direction, etc)
if self.is_master then
-- Wait one tick before finding the slave so that we're sure it has been created.
local --[[persistable:swing_door_creation]] function callback()
local slave_type = "swing_door_left"
self.slave = world:getObject(x - 1, y, slave_type) or world:getObject(x, y - 1, slave_type) or nil
self.slave = self.world:getObject(x - 1, y, slave_type) or self.world:getObject(x, y - 1, slave_type) or nil
self.slave:setAsSlave(self)
self.ticks = false
end

View File

@@ -34,20 +34,20 @@ object.orientations = {
},
}
--! An `Object` which drops of emergency patients.
--! An `Object` which drops off emergency patients.
class "Helicopter" (Object)
---@type Helicopter
local Helicopter = _G["Helicopter"]
function Helicopter:Helicopter(world, object_type, hospital, direction, etc)
function Helicopter:Helicopter(hospital, object_type, direction, etc)
local x, y = hospital:getHeliportPosition()
-- Helicoptor needs to land below tile to be positioned correctly
-- Helicopter needs to land below tile to be positioned correctly
y = y + 1
self:Object(world, object_type, x, y, direction, etc)
self:Object(hospital, object_type, x, y, direction, etc)
self.th:makeInvisible()
self:setPosition(0, -600)
self.phase = -60
self.phase = -120
self.hospital = hospital
-- TODO: Shadow: 3918
hospital.emergency_patients = {}
@@ -61,7 +61,7 @@ function Helicopter:tick()
elseif phase == 60 then
self:setSpeed(0, 0)
self.spawned_patients = 0
elseif phase == 80 then
elseif phase == 85 then
if self.spawned_patients < self.hospital.emergency.victims then
self:spawnPatient()
phase = 60
@@ -82,17 +82,22 @@ function Helicopter:spawnPatient()
local patient = self.world:newEntity("Patient", 2)
patient:setDisease(hospital.emergency.disease)
patient.diagnosis_progress = 1
patient.is_emergency = self.spawned_patients
patient:setDiagnosed()
patient:setMood("emergency", "activate")
patient.is_emergency = self.spawned_patients
hospital.emergency_patients[#hospital.emergency_patients + 1] = patient
hospital.emergency_patients[self.spawned_patients] = patient
local x, y = hospital:getHeliportSpawnPosition()
patient:setNextAction(SpawnAction("spawn", {x = x, y = y}):setOffset({y = 1}))
patient:setHospital(hospital)
-- TODO: If new combined diseases are added this will not work correctly anymore.
patient.cure_rooms_visited = #patient.disease.treatment_rooms - 1
local no_of_rooms = #patient.disease.treatment_rooms
patient:queueAction(SeekRoomAction(patient.disease.treatment_rooms[no_of_rooms]))
-- a spawning emergency patient might rather leave than pay
if not patient:agreesToPay(patient.disease.id) then
patient:goHome("over_priced", patient.disease.id)
else
patient:queueAction(SeekRoomAction(patient.disease.treatment_rooms[no_of_rooms]))
end
end
return object

View File

@@ -53,12 +53,13 @@ class "Litter" (Entity)
---@type Litter
local Litter = _G["Litter"]
function Litter:Litter(world, object_type, x, y, direction, etc)
function Litter:Litter(hospital, object_type, x, y, direction, etc)
local th = TH.animation()
self:Entity(th)
self.ticks = object_type.ticks
self.object_type = object_type
self.world = world
self.hospital = hospital
self.world = hospital.world
self:setTile(x, y)
end
@@ -86,22 +87,20 @@ function Litter:setLitterType(anim_type, mirrorFlag)
error("Unknown litter type")
end
if self:isCleanable() then
local hospital = self.world:getHospital(self.tile_x, self.tile_y)
hospital:addHandymanTask(self, "cleaning", 1, self.tile_x, self.tile_y)
self.hospital:addHandymanTask(self, "cleaning", 1, self.tile_x, self.tile_y)
end
end
end
--! Remove the litter from the world.
function Litter:remove()
assert(self:isCleanable())
assert(self:isCleanable()or TheApp.config.remove_destroyed_rooms)
if self.tile_x then
self.world:removeObjectFromTile(self, self.tile_x, self.tile_y)
local hospital = self.world:getHospital(self.tile_x, self.tile_y)
local taskIndex = hospital:getIndexOfTask(self.tile_x, self.tile_y, "cleaning", self)
hospital:removeHandymanTask(taskIndex, "cleaning")
local taskIndex = self.hospital:getIndexOfTask(self.tile_x, self.tile_y, "cleaning", self)
self.hospital:removeHandymanTask(taskIndex, "cleaning")
else
print("Warning: Removing litter that has already been removed.")
end
@@ -140,7 +139,7 @@ end
function Litter:afterLoad(old, new)
if old < 52 then
if self.tile_x then
self.world.hospitals[1]:addHandymanTask(self, "cleaning", 1, self.tile_x, self.tile_y)
self.hospital:addHandymanTask(self, "cleaning", 1, self.tile_x, self.tile_y)
else
-- This object was not properly removed from the world.
self.world:destroyEntity(self)
@@ -148,15 +147,18 @@ function Litter:afterLoad(old, new)
end
if old < 54 then
if not self:isCleanable() then
local hospital = self.world:getHospital(self.tile_x, self.tile_y)
local taskIndex = hospital:getIndexOfTask(self.tile_x, self.tile_y, "cleaning", self)
hospital:removeHandymanTask(taskIndex, "cleaning")
local taskIndex = self.hospital:getIndexOfTask(self.tile_x, self.tile_y, "cleaning", self)
self.hospital:removeHandymanTask(taskIndex, "cleaning")
end
end
if old < 121 then
self.ticks = object.ticks
end
if old < 151 then
self.hospital = self.world:getHospital(self.tile_x, self.tile_y)
end
end
return object

View File

@@ -77,8 +77,9 @@ object.orientations = {
-- * dying: 1953
-- * dead: 1954
local days_between_states = 75
-- TH uses rough 50 days between state transitions
-- to approximate that same average rate at ideal temperatures
local days_between_states = 64
-- days before we reannouncing our watering status if we were unreachable
local days_unreachable = 10
@@ -89,15 +90,16 @@ class "Plant" (Object)
---@type Plant
local Plant = _G["Plant"]
function Plant:Plant(world, object_type, x, y, direction, etc)
function Plant:Plant(hospital, object_type, x, y, direction, etc)
-- It doesn't matter which direction the plant is facing. It will be rotated so that an approaching
-- handyman uses the correct usage animation when appropriate.
self:Object(world, object_type, x, y, direction, etc)
self:Object(hospital, object_type, x, y, direction, etc)
self.current_state = 0
self.base_frame = self.th:getFrame()
self.days_left = days_between_states
self.unreachable = false
self.unreachable_counter = days_unreachable
self.phases = 5
end
--! Goes one step forward (or backward) in the states of the plant.
@@ -107,7 +109,7 @@ function Plant:setNextState(restoring)
if self.current_state > 0 then
self.current_state = self.current_state - 1
end
elseif self.current_state < 5 then
elseif self.current_state < self.phases - 1 then
self.current_state = self.current_state + 1
end
@@ -157,13 +159,7 @@ end
--! Returns whether the plant is in need of watering right now.
function Plant:needsWatering()
if self.current_state == 0 then
if self.days_left < 10 then
return true
end
else
return true
end
return self.current_state ~= 0
end
--! When the plant needs water it periodically calls for a nearby handyman.
@@ -229,7 +225,8 @@ function Plant:createHandymanActions(handyman)
handyman:queueAction(AnswerCallAction())
end
--! When a handyman should go to the plant he should approach it from the closest reachable tile.
--! When a handyman should go to the plant he should approach it from the
-- closest reachable tile within hospital buildings.
--!param from_x (integer) The x coordinate of tile to calculate from.
--!param from_y (integer) The y coordinate of tile to calculate from.
function Plant:getBestUsageTileXY(from_x, from_y)
@@ -243,8 +240,8 @@ function Plant:getBestUsageTileXY(from_x, from_y)
for _, point in ipairs(access_points) do
local dest_x, dest_y = self.tile_x + point.dx, self.tile_y + point.dy
local room_there = self.world:getRoom(dest_x, dest_y)
if room_here == room_there then
local distance = self.world:getPathDistance(from_x, from_y, self.tile_x + point.dx, self.tile_y + point.dy)
if room_here == room_there and self.hospital:isInHospital(dest_x, dest_y) then
local distance = self.world:getPathDistance(from_x, from_y, dest_x, dest_y)
if distance and (not best_point or shortest > distance) then
best_point = point
shortest = distance
@@ -292,12 +289,18 @@ function Plant:onClick(ui, button)
Object.onClick(self, ui, button)
end
function Plant:isPleasing()
if not self.ticks then
--! Plant health/state should be used on evaluations of pleasantness
--! returns (integer) score of this plants health, 1 to 5 (best)
function Plant:isPleasingFactor()
return self.phases - self.current_state
end
--! Check if a plant is dying or about to start dying
function Plant:isDying()
if self.current_state ~= 0 or self.days_left < 3 then
return true
else
return false
end
return false
end
function Plant:onDestroy()
@@ -312,6 +315,11 @@ function Plant:afterLoad(old, new)
if old < 52 then
self.hospital = self.world:getLocalPlayerHospital()
end
if old < 150 then
self.phases = 5
end
Object.afterLoad(self, old, new)
end

View File

@@ -55,8 +55,8 @@ class "Rathole" (Object)
---@type Rathole
local Rathole = _G["Rathole"]
function Rathole:Rathole(world, oject_type, x, y, direction, etc)
self:Object(world, oject_type, x, y, direction, etc)
function Rathole:Rathole(hospital, oject_type, x, y, direction, etc)
self:Object(hospital, oject_type, x, y, direction, etc)
end
return object

View File

@@ -106,7 +106,7 @@ function ReceptionDesk:tick()
if class.is(queue_front, Inspector) then
local inspector = queue_front
if not inspector.going_home then
local epidemic = self.world:getLocalPlayerHospital().epidemic
local epidemic = self.hospital.epidemic
if epidemic then
-- The result of the epidemic may already by determined
-- i.e if an infected patient has left the hospital
@@ -151,11 +151,10 @@ function ReceptionDesk:checkForNearbyStaff()
end
local nearest_staff, nearest_d
local world = self.world
local use_x, use_y = self:getSecondaryUsageTile()
for _, entity in ipairs(self.world.entities) do
if entity.humanoid_class == "Receptionist" and not entity.associated_desk and not entity.fired then
local distance = world.pathfinder:findDistance(entity.tile_x, entity.tile_y, use_x, use_y)
local distance = self.world.pathfinder:findDistance(entity.tile_x, entity.tile_y, use_x, use_y)
if not nearest_d or distance < nearest_d then
nearest_staff = entity
nearest_d = distance
@@ -198,14 +197,14 @@ function ReceptionDesk:onDestroy()
self.reserved_for = nil
-- Find a new reception desk for the receptionist
local world = receptionist.world
world:findObjectNear(receptionist, "reception_desk", nil, function(x, y)
local obj = world:getObject(x, y, "reception_desk")
-- Make sure we are not selecting the same desk again
if obj and obj ~= self then
return obj:occupy(receptionist)
end
end)
self.world:findObjectNear(receptionist, "reception_desk", nil,
function(x, y)
local obj = self.world:getObject(x, y, "reception_desk")
-- Make sure we are not selecting the same desk again
if obj and obj ~= self then
return obj:occupy(receptionist)
end
end)
end
self.queue:rerouteAllPatients(SeekReceptionAction())

View File

@@ -26,7 +26,7 @@ strict_declare_global "unpermanent"
local th_getfenv
local th_getupvalue
if _G._VERSION == "Lua 5.2" or _G._VERSION == "Lua 5.3" then
if _G._VERSION == "Lua 5.2" or _G._VERSION == "Lua 5.3" or _G._VERSION == "Lua 5.4" then
th_getfenv = function(f)
local _, val = nil, nil
if type(f) == "function" then
@@ -255,15 +255,19 @@ end
--!param filename (string) Path of the file to write.
function SaveGameFile(filename)
local data = SaveGame()
local f = assert(io.open(filename, "wb"))
local f = TheApp:writeToFileOrTmp(filename, "wb")
f:write(data)
f:close()
end
--! Puts loaded file into the game
--!param data The file
function LoadGame(data)
--local status, res = xpcall(function()
local objtable = MakePermanentObjectsTable(true)
local state = assert(persist.load(data, objtable))
-- Check the game we're loading is compatible with program
if not TheApp:checkCompatibility(state.world.savegame_version) then return end
state.ui:resync(TheApp.ui)
TheApp.ui = state.ui
TheApp.world = state.world

Some files were not shown because too many files have changed in this diff Show More