diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7fd67c4a..967b4c91 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,16 +13,22 @@ jobs: steps: - name: Download Colobot dependencies run: sudo apt-get update && sudo apt-get install -y --no-install-recommends build-essential cmake libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libsndfile1-dev libvorbis-dev libogg-dev libpng-dev libglew-dev libopenal-dev libboost-dev libboost-system-dev libboost-filesystem-dev libboost-regex-dev libphysfs-dev gettext git po4a vorbis-tools librsvg2-bin xmlstarlet - # TODO: migrate colobot-lint to GitHub Actions + - name: Download colobot-lint dependencies + run: sudo apt-get install -y --no-install-recommends clang-3.6 libtinyxml2.6.2v5 + - run: mkdir -p /tmp/colobot-lint - name: Download colobot-lint + uses: dawidd6/action-download-artifact@v2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + repo: colobot/colobot-lint + branch: master + workflow: build.yml + workflow_conclusion: success + name: colobot-lint + path: /tmp/colobot-lint/archive + - name: Unpack colobot-lint + working-directory: /tmp/colobot-lint run: | - sudo apt-get install -y --no-install-recommends clang-3.6 libtinyxml2.6.2v5 - mkdir -p /tmp/colobot-lint - cd /tmp/colobot-lint - wget -O colobot-lint.zip "https://compiled.colobot.info/job/colobot/job/colobot-lint/job/dev/lastSuccessfulBuild/artifact/*zip*/archive.zip" - - # Unzip the archive - unzip ./colobot-lint.zip # Workaround for Clang not finding system headers mkdir ./bin mv ./archive/build/colobot-lint ./bin/ @@ -73,15 +79,12 @@ jobs: with: name: HTML results path: build/html_report - - run: pip install requests - - name: Send linter results to GitHub + - name: Generate GitHub annotations JSON and process check result shell: python - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | import os import sys - import requests + import json import xml.etree.ElementTree as ET OVERALL_STABLE_RULES=[ @@ -134,18 +137,6 @@ jobs: "whitespace", ] - # None of the available actions seem to do what I want, they all do stupid things like adding another check... let's just do it manually - # GitHub also doesn't seem to provide you with the check suite or check run ID, so we have to get it from the action ID via the API - s = requests.Session() - s.headers.update({ - 'Authorization': 'token ' + os.environ['GITHUB_TOKEN'], - 'Accept': 'application/vnd.github.antiope-preview+json' # Annotations are still technically a preview feature of the API - }) - action_run = s.get(os.environ['GITHUB_API_URL'] + "/repos/" + os.environ['GITHUB_REPOSITORY'] + "/actions/runs/" + os.environ['GITHUB_RUN_ID']).json() - check_suite = s.get(action_run['check_suite_url']).json() - check_suite_runs = s.get(check_suite['check_runs_url']).json() - check_run = check_suite_runs['check_runs'][0] # NOTE: This assumes that the 'lint' job is the first one in the workflow. You could find it by name if you really wanted. - def we_care_about(file_name, type): if 'CBot' in file_name: return type in OVERALL_STABLE_RULES @@ -154,6 +145,7 @@ jobs: results = ET.parse('build/colobot_lint_report.xml') annotations = [] + stable_annotations = [] for error in results.find('errors').findall('error'): location = error.find('location') file_name = os.path.relpath(location.get('file'), os.environ['GITHUB_WORKSPACE']) @@ -168,38 +160,35 @@ jobs: elif severity == 'information': gh_severity = 'notice' - if not we_care_about(file_name, type): - # don't send the unstable rules to github at all as there are way too many of them and it would overload the API rate limit - continue - - print('{}:{}: [{}] {}'.format(file_name, line_num, type, msg)) - - annotations.append({ + annotation = { 'path': file_name, 'start_line': line_num, 'end_line': line_num, 'annotation_level': gh_severity, 'title': type, 'message': msg - }) - - summary = 'colobot-lint found {} issues'.format(len(annotations)) - all_ok = len(annotations) == 0 - - # Annotations have to be sent in batches of 50 - first = True - while first or len(annotations) > 0: - first = False - to_send = annotations[:50] - annotations = annotations[50:] - data = { - 'output': { - 'title': summary, - 'summary': summary, - 'annotations': to_send - } } - r = s.patch(check_run['url'], json=data) - r.raise_for_status() + annotations.append(annotation) + if we_care_about(file_name, type): + # don't send the unstable rules to github at all as there are way too many of them and it would overload the API rate limit + stable_annotations.append(annotation) + print('{}:{}: [{}] {}'.format(file_name, line_num, type, msg)) + + summary = 'colobot-lint found {} issues'.format(len(stable_annotations)) + all_ok = len(stable_annotations) == 0 + print('Conclusion: {}'.format(summary)) + + with open("build/annotations.json", "w") as f: + json.dump(annotations, f, indent=4) + with open("build/stable_annotations.json", "w") as f: + json.dump(stable_annotations, f, indent=4) sys.exit(0 if all_ok else 1) + - name: Upload results (JSON) + uses: actions/upload-artifact@v2 + with: + name: JSON results + path: | + build/annotations.json + build/stable_annotations.json + if: ${{ always() }} diff --git a/.github/workflows/lint_upload_results.yml b/.github/workflows/lint_upload_results.yml new file mode 100644 index 00000000..3adb3c54 --- /dev/null +++ b/.github/workflows/lint_upload_results.yml @@ -0,0 +1,67 @@ +name: Linter upload results + +# Upload linter results after succesful linter run +# This is done in a separate workflow to safely use the read-write GitHub token +# See https://securitylab.github.com/research/github-actions-preventing-pwn-requests + +on: + workflow_run: + workflows: ["Linter"] + types: + - completed + +jobs: + lint_upload: + runs-on: ubuntu-16.04 + steps: + - run: pip install requests + - name: Download linter results + uses: dawidd6/action-download-artifact@v2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + workflow: lint.yml + run_id: ${{ github.event.workflow_run.id }} + name: JSON results + path: results + - name: Send linter results to GitHub + shell: python + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUN_ID: ${{ github.event.workflow_run.id }} + run: | + import os + import json + import requests + + # Load the results from the lint job artifact + with open("results/stable_annotations.json", "r") as f: + annotations = json.load(f) + summary = 'colobot-lint found {} issues'.format(len(annotations)) + + # None of the available actions seem to do what I want, they all do stupid things like adding another check... let's just do it manually + # GitHub also doesn't seem to provide you with the check suite or check run ID, so we have to get it from the action ID via the API + s = requests.Session() + s.headers.update({ + 'Authorization': 'token ' + os.environ['GITHUB_TOKEN'], + 'Accept': 'application/vnd.github.antiope-preview+json' # Annotations are still technically a preview feature of the API + }) + action_run = s.get(os.environ['GITHUB_API_URL'] + "/repos/" + os.environ['GITHUB_REPOSITORY'] + "/actions/runs/" + os.environ['RUN_ID']).json() + check_suite = s.get(action_run['check_suite_url']).json() + check_suite_runs = s.get(check_suite['check_runs_url']).json() + check_run = check_suite_runs['check_runs'][0] # NOTE: This assumes that the 'lint' job is the first one in the workflow. You could find it by name if you really wanted. + + # Annotations have to be sent in batches of 50 + first = True + while first or len(annotations) > 0: + first = False + to_send = annotations[:50] + annotations = annotations[50:] + data = { + 'output': { + 'title': summary, + 'summary': summary, + 'annotations': to_send + } + } + r = s.patch(check_run['url'], json=data) + r.raise_for_status() diff --git a/.github/workflows/verify-pr-target.yml b/.github/workflows/verify-pr-target.yml index 09cadb19..c5f388d7 100644 --- a/.github/workflows/verify-pr-target.yml +++ b/.github/workflows/verify-pr-target.yml @@ -1,14 +1,21 @@ name: Verify pull request target -on: [pull_request] +on: [pull_request_target] jobs: check_pr_target: runs-on: ubuntu-latest steps: - - name: Wrong pull request target - run: echo "This pull request targets the master branch. Please edit the pull request to target dev." && exit 1 + - name: Send comment if wrong pull request target if: github.base_ref == 'master' + uses: peter-evans/create-or-update-comment@v1 + with: + issue-number: ${{ github.event.number }} + body: | + Hey! This pull request targets the `master` branch. You should probably target `dev` instead. Make sure to read the [contributing guidelines](https://github.com/colobot/colobot/blob/master/CONTRIBUTING.md#submitting-pull-requests) and [edit the target branch if necessary](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-base-branch-of-a-pull-request). + - name: Wrong pull request target + if: github.base_ref == 'master' + run: echo "This pull request targets the master branch. Please edit the pull request to target dev." && exit 1 - name: Correct pull request target + if: github.base_ref != 'master' run: echo "This pull request targets the correct branch." && exit 0 - if: github.base_ref != 'master' \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 63f1ace6..90843488 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,12 +189,12 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") set(NORMAL_CXX_FLAGS "/wd\"4244\" /wd\"4309\" /wd\"4800\" /wd\"4996\" /wd\"4351\" /EHsc") # disable some useless warnings if(MSVC_STATIC) set(RELEASE_CXX_FLAGS "/MT /Ox") - set(DEBUG_CXX_FLAGS "/MTd /ZI") + set(DEBUG_CXX_FLAGS "/MTd /Od /ZI") else(MSVC_STATIC) set(RELEASE_CXX_FLAGS "/MD /Ox") - set(DEBUG_CXX_FLAGS "/MDd /ZI") + set(DEBUG_CXX_FLAGS "/MDd /Od /ZI") endif() - set(TEST_CXX_FLAGS "") + set(TEST_CXX_FLAGS "${DEBUG_CXX_FLAGS}") add_definitions(-DNOEXCEPT= -DHAS_MSVC_EXCEPTION_BUG) # Needed for Debug information (it's set to "No" by default for some reason) diff --git a/README.md b/README.md index a393a47a..4bb5e62a 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ On some Linux distributions there are also distribution packages available: * Arch Linux (AUR): https://aur.archlinux.org/packages/colobot-gold * openSUSE: http://software.opensuse.org/download.html?project=games&package=colobot * Fedora: https://src.fedoraproject.org/rpms/colobot + * Guix https://guix.gnu.org/en/packages/colobot-0.1.12-alpha/ ## Compiling and running the game diff --git a/po/colobot.pot b/po/colobot.pot index b1b3518d..442eafd6 100644 --- a/po/colobot.pot +++ b/po/colobot.pot @@ -87,6 +87,9 @@ msgstr "" msgid "Missions+" msgstr "" +msgid "Mods" +msgstr "" + msgid "Chapters:" msgstr "" @@ -169,6 +172,23 @@ msgstr "" msgid "This menu is for userlevels from mods, but you didn't install any" msgstr "" +msgid "Could not open the file explorer!" +msgstr "" + +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "Keyword help(\\key cbot;)" msgstr "" @@ -283,6 +303,42 @@ msgstr "" msgid "%s: %d pts" msgstr "" +msgid "Mods:" +msgstr "" + +msgid "Information:" +msgstr "" + +msgid "Description:" +msgstr "" + +msgid "Enable\\Enable the selected mod" +msgstr "" + +msgid "Disable\\Disable the selected mod" +msgstr "" + +msgid "Unknown author" +msgstr "" + +msgid "by" +msgstr "" + +msgid "Version" +msgstr "" + +msgid "Website" +msgstr "" + +msgid "Changes" +msgstr "" + +msgid "No description." +msgstr "" + +msgid "No changes." +msgstr "" + msgid "Code battle" msgstr "" @@ -319,6 +375,9 @@ msgstr "" msgid "SatCom" msgstr "" +msgid "Mods\\Mod manager" +msgstr "" + msgid "Change player\\Change player" msgstr "" @@ -349,6 +408,24 @@ msgstr "" msgid "Play\\Start mission!" msgstr "" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + +msgid "Open Directory\\Open the mods directory" +msgstr "" + +msgid "Apply\\Apply the current mod configuration" +msgstr "" + +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Device\\Driver and resolution settings" msgstr "" diff --git a/po/cs.po b/po/cs.po index 34d59283..453ded95 100644 --- a/po/cs.po +++ b/po/cs.po @@ -128,6 +128,9 @@ msgstr "Vzhled\\Upravte svůj vzhled" msgid "Apply changes\\Activates the changed settings" msgstr "Uložit změny\\Aktivovat změny nastavení" +msgid "Apply\\Apply the current mod configuration" +msgstr "" + msgid "Appropriate constructor missing" msgstr "Chybí vhodný konstruktor" @@ -374,6 +377,9 @@ msgstr "Změnit kameru\\Přepíná mezi kamerou na robotu a za robotem" msgid "Change player\\Change player" msgstr "Změnit hráče\\Změnit hráče" +msgid "Changes" +msgstr "" + msgid "Chapters:" msgstr "Kapitoly:" @@ -443,6 +449,12 @@ msgstr "Kopírovat" msgid "Copy (Ctrl+C)" msgstr "Kopírovat (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + msgid "Current mission saved" msgstr "Současná mise uložena" @@ -476,6 +488,9 @@ msgstr "Vrtná věž" msgid "Descend\\Reduces the power of the jet" msgstr "Klesat\\Snížit tah tryskového motoru" +msgid "Description:" +msgstr "" + msgid "Destroy" msgstr "Zbourat" @@ -488,6 +503,9 @@ msgstr "Drtič" msgid "Device\\Driver and resolution settings" msgstr "Obrazovka\\Nastavení grafické karty a rozlišení" +msgid "Disable\\Disable the selected mod" +msgstr "" + msgid "Dividing by zero" msgstr "Dělení nulou" @@ -504,6 +522,9 @@ msgstr "Dveře blokuje robot nebo jiný objekt" msgid "Down (\\key gdown;)" msgstr "Dolů (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Drawer bot" msgstr "Tužkobot" @@ -531,6 +552,9 @@ msgstr "Vejce" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "" + msgid "End of block missing" msgstr "Chybí konec bloku" @@ -757,6 +781,9 @@ msgstr "Infikováno virem; dočasně mimo provoz" msgid "Information exchange post" msgstr "Komunikační stanice" +msgid "Information:" +msgstr "" + msgid "Instruction \"break\" outside a loop" msgstr "Příkaz \"break\" mimo cyklus" @@ -910,6 +937,15 @@ msgstr "Mise+" msgid "Missions\\Select mission" msgstr "Mise\\Vyberte misi" +msgid "Mods" +msgstr "" + +msgid "Mods:" +msgstr "" + +msgid "Mods\\Mod manager" +msgstr "" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Vodorovné převrácení posunu\\Při vodorovném posunu kamery myší pousouvat opačným směrem" @@ -958,6 +994,12 @@ msgstr "Další objekt\\Vybere následující objekt" msgid "No" msgstr "Ne" +msgid "No changes." +msgstr "" + +msgid "No description." +msgstr "" + msgid "No energy in the subsoil" msgstr "Pod povrchem není zdroj energie" @@ -1078,6 +1120,9 @@ msgstr "Otevřít" msgid "Open (Ctrl+O)" msgstr "Otevřít (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "" + msgid "Opening brace missing" msgstr "Chybí levá složená závorka" @@ -1288,6 +1333,9 @@ msgstr "Červená vlajka" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Odlesky na tlačítkách\\Blyštivá tlačítka" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Remains of Apollo mission" msgstr "Pozůstatky mise Apollo" @@ -1558,6 +1606,10 @@ msgstr "Filtrování textur\\Filtrování textur" msgid "Textures" msgstr "Textury" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + msgid "The battle has ended" msgstr "Souboj skončil" @@ -1570,9 +1622,16 @@ msgstr "Funkce nevrátila žádnou hodnotu" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "Mise ještě nebyla splněna (pro podrobnosti stiskněte \\key help;)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + msgid "The types of the two operands are incompatible" msgstr "Operaci nelze provést s operandy těchto dvou typů" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "This class already exists" msgstr "Tato třída již existuje" @@ -1697,6 +1756,9 @@ msgstr "Jednotka" msgid "Unknown Object" msgstr "Neznámý objekt" +msgid "Unknown author" +msgstr "" + msgid "Unknown command" msgstr "Neznámý příkaz" @@ -1709,6 +1771,9 @@ msgstr "Neznámá funkce" msgid "Up (\\key gup;)" msgstr "Vzhůru (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Uranium deposit (site for derrick)" msgstr "Uranové ložisko (místo pro vrtnou věž)" @@ -1730,6 +1795,9 @@ msgstr "Proměnná nebyla nastavena" msgid "Vault" msgstr "Trezor" +msgid "Version" +msgstr "" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "" @@ -1748,6 +1816,9 @@ msgstr "Vosa byla smrtelně raněna" msgid "Waste" msgstr "Odpad" +msgid "Website" +msgstr "" + msgid "Wheeled builder" msgstr "" @@ -1781,6 +1852,9 @@ msgstr "Létající detektor" msgid "Withdraw shield (\\key action;)" msgstr "Vypnout štít (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + msgid "Worm" msgstr "Červ" @@ -1931,6 +2005,9 @@ msgstr "\\Fialové vlajky" msgid "\\Yellow flags" msgstr "\\Žluté vlajky" +msgid "by" +msgstr "" + msgid "colobot.info" msgstr "colobot.info" diff --git a/po/de.po b/po/de.po index 727ba367..7bcffe5b 100644 --- a/po/de.po +++ b/po/de.po @@ -129,6 +129,9 @@ msgstr "Aussehen\\Erscheinungsbild des Astronauten einstellen" msgid "Apply changes\\Activates the changed settings" msgstr "Änderungen anwenden\\Getätigte Einstellungen anwenden" +msgid "Apply\\Apply the current mod configuration" +msgstr "" + msgid "Appropriate constructor missing" msgstr "Es gibt keinen geeigneten Konstruktor" @@ -375,6 +378,9 @@ msgstr "Andere Kamera\\Sichtpunkt einstellen" msgid "Change player\\Change player" msgstr "Anderer Spieler\\Spielername ändern" +msgid "Changes" +msgstr "" + msgid "Chapters:" msgstr "Liste der Kapitel:" @@ -444,6 +450,12 @@ msgstr "Kopieren" msgid "Copy (Ctrl+C)" msgstr "Kopieren (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + msgid "Current mission saved" msgstr "Mission gespeichert" @@ -477,6 +489,9 @@ msgstr "Bohrturm" msgid "Descend\\Reduces the power of the jet" msgstr "Sinken\\Leistung des Triebwerks drosseln" +msgid "Description:" +msgstr "" + msgid "Destroy" msgstr "Zerstören" @@ -489,6 +504,9 @@ msgstr "Einstampfer" msgid "Device\\Driver and resolution settings" msgstr "Bildschirm\\Driver und Bildschirmauflösung" +msgid "Disable\\Disable the selected mod" +msgstr "" + msgid "Dividing by zero" msgstr "Division durch Null" @@ -505,6 +523,9 @@ msgstr "Die Türen werden von einem Gegenstand blockiert" msgid "Down (\\key gdown;)" msgstr "Sinkt (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Drawer bot" msgstr "Zeichner" @@ -532,6 +553,9 @@ msgstr "Ei" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "" + msgid "End of block missing" msgstr "Es fehlt eine geschlossene geschweifte Klammer \"}\" (Ende des Blocks)" @@ -759,6 +783,9 @@ msgstr "Von Virus infiziert, zeitweise außer Betrieb" msgid "Information exchange post" msgstr "Infoserver" +msgid "Information:" +msgstr "" + msgid "Instruction \"break\" outside a loop" msgstr "Anweisung \"break\" außerhalb einer Schleife" @@ -926,6 +953,15 @@ msgstr "Missionen+" msgid "Missions\\Select mission" msgstr "Missionen\\Aufbruch ins Weltall" +msgid "Mods" +msgstr "" + +msgid "Mods:" +msgstr "" + +msgid "Mods\\Mod manager" +msgstr "" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Umkehr X\\Umkehr der Kameradrehung X-Achse" @@ -974,6 +1010,12 @@ msgstr "Nächstes auswählen\\Nächstes Objekt auswählen" msgid "No" msgstr "Nein" +msgid "No changes." +msgstr "" + +msgid "No description." +msgstr "" + msgid "No energy in the subsoil" msgstr "Kein unterirdisches Energievorkommen" @@ -1094,6 +1136,9 @@ msgstr "Öffnen" msgid "Open (Ctrl+O)" msgstr "Öffnen (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "" + msgid "Opening brace missing" msgstr "Es fehlt eine offene geschweifte Klammer\"{\"" @@ -1305,6 +1350,9 @@ msgstr "Rote Fahne" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Glänzende Tasten\\Glänzende Tasten in den Menüs" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Remains of Apollo mission" msgstr "Überreste einer Apollo-Mission" @@ -1575,6 +1623,10 @@ msgstr "Texturfilterung\\Texturfilterung" msgid "Textures" msgstr "Texturen" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + msgid "The battle has ended" msgstr "" @@ -1587,9 +1639,16 @@ msgstr "Die Funktion hat kein Ergebnis zurückgegeben" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "Mission noch nicht beendet (Drücken Sie auf \\key help; für weitere Informationen)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + msgid "The types of the two operands are incompatible" msgstr "Die zwei Operanden sind nicht kompatibel" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "This class already exists" msgstr "Diese Klasse gibt es schon" @@ -1714,6 +1773,9 @@ msgstr "Einheit" msgid "Unknown Object" msgstr "Das Objekt existiert nicht" +msgid "Unknown author" +msgstr "" + msgid "Unknown command" msgstr "Befehl unbekannt" @@ -1726,6 +1788,9 @@ msgstr "Unbekannte Funktion" msgid "Up (\\key gup;)" msgstr "Steigt (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Uranium deposit (site for derrick)" msgstr "Markierung für unterirdisches Platinvorkommen" @@ -1747,6 +1812,9 @@ msgstr "Der Wert dieser Variable wurde nicht definiert" msgid "Vault" msgstr "Bunker" +msgid "Version" +msgstr "" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "" @@ -1765,6 +1833,9 @@ msgstr "Wespe tödlich verwundet" msgid "Waste" msgstr "Abfall" +msgid "Website" +msgstr "" + msgid "Wheeled builder" msgstr "" @@ -1798,6 +1869,9 @@ msgstr "Schnüffler" msgid "Withdraw shield (\\key action;)" msgstr "Schutzschild einholen (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + msgid "Worm" msgstr "Wurm" @@ -1946,6 +2020,9 @@ msgstr "\\Violette Fahne" msgid "\\Yellow flags" msgstr "\\Gelbe Fahne" +msgid "by" +msgstr "" + msgid "colobot.info" msgstr "colobot.info" diff --git a/po/fr.po b/po/fr.po index 506ba564..a6375ce7 100644 --- a/po/fr.po +++ b/po/fr.po @@ -128,6 +128,9 @@ msgstr "Aspect\\Choisir votre aspect" msgid "Apply changes\\Activates the changed settings" msgstr "Appliquer les changements\\Active les changements effectués" +msgid "Apply\\Apply the current mod configuration" +msgstr "" + msgid "Appropriate constructor missing" msgstr "Constructeur approprié manquant" @@ -377,6 +380,9 @@ msgstr "Changement de caméra\\Autre de point de vue" msgid "Change player\\Change player" msgstr "Autre joueur\\Choix du nom du joueur" +msgid "Changes" +msgstr "" + msgid "Chapters:" msgstr "Liste des chapitres :" @@ -446,6 +452,12 @@ msgstr "Copier" msgid "Copy (Ctrl+C)" msgstr "Copier (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + msgid "Current mission saved" msgstr "Enregistrement effectué" @@ -479,6 +491,9 @@ msgstr "Derrick" msgid "Descend\\Reduces the power of the jet" msgstr "Descendre\\Diminuer la puissance du réacteur" +msgid "Description:" +msgstr "" + msgid "Destroy" msgstr "Détruire" @@ -491,6 +506,9 @@ msgstr "Destructeur" msgid "Device\\Driver and resolution settings" msgstr "Affichage\\Pilote et résolution d'affichage" +msgid "Disable\\Disable the selected mod" +msgstr "" + msgid "Dividing by zero" msgstr "Division par zéro" @@ -507,6 +525,9 @@ msgstr "Portes bloquées par un robot ou un objet" msgid "Down (\\key gdown;)" msgstr "Descend (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Drawer bot" msgstr "Robot dessinateur" @@ -534,6 +555,9 @@ msgstr "Oeuf" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "" + msgid "End of block missing" msgstr "Il manque la fin du bloc" @@ -761,6 +785,9 @@ msgstr "Infecté par un virus; ne fonctionne plus temporairement" msgid "Information exchange post" msgstr "Station relais" +msgid "Information:" +msgstr "" + msgid "Instruction \"break\" outside a loop" msgstr "Instruction \"break\" en dehors d'une boucle" @@ -928,6 +955,15 @@ msgstr "Missions+" msgid "Missions\\Select mission" msgstr "Missions\\La grande aventure" +msgid "Mods" +msgstr "" + +msgid "Mods:" +msgstr "" + +msgid "Mods\\Mod manager" +msgstr "" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Inversion souris X\\Inversion de la rotation lorsque la souris touche un bord" @@ -976,6 +1012,12 @@ msgstr "Sélectionner l'objet suivant\\Sélectionner l'objet suivant" msgid "No" msgstr "Non" +msgid "No changes." +msgstr "" + +msgid "No description." +msgstr "" + msgid "No energy in the subsoil" msgstr "Pas d'énergie en sous-sol" @@ -1096,6 +1138,9 @@ msgstr "Ouvrir" msgid "Open (Ctrl+O)" msgstr "Ouvrir (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "" + msgid "Opening brace missing" msgstr "Début d'un bloc attendu" @@ -1307,6 +1352,9 @@ msgstr "Drapeau rouge" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Reflets sur les boutons\\Boutons brillants" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Remains of Apollo mission" msgstr "Vestige d'une mission Apollo" @@ -1577,6 +1625,10 @@ msgstr "Filtrage de textures\\Filtrage de textures" msgid "Textures" msgstr "Textures" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + msgid "The battle has ended" msgstr "La bataille est terminée" @@ -1589,9 +1641,16 @@ msgstr "La fonction n'a pas retourné de résultat" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "La mission n'est pas terminée (appuyez sur \\key help; pour plus de détails)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + msgid "The types of the two operands are incompatible" msgstr "Les deux opérandes ne sont pas de types compatibles" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "This class already exists" msgstr "Cette classe existe déjà" @@ -1716,6 +1775,9 @@ msgstr "Unité" msgid "Unknown Object" msgstr "Objet inconnu" +msgid "Unknown author" +msgstr "" + msgid "Unknown command" msgstr "Commande inconnue" @@ -1728,6 +1790,9 @@ msgstr "Routine inconnue" msgid "Up (\\key gup;)" msgstr "Monte (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Uranium deposit (site for derrick)" msgstr "Emplacement pour un derrick (minerai d'uranium)" @@ -1749,6 +1814,9 @@ msgstr "Variable non initialisée" msgid "Vault" msgstr "Coffre-fort" +msgid "Version" +msgstr "" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "Synchronisation verticale :\\Réduit la fréquence d'images par seconde à afficher." @@ -1767,6 +1835,9 @@ msgstr "Guêpe mortellement touchée" msgid "Waste" msgstr "Déchet" +msgid "Website" +msgstr "" + msgid "Wheeled builder" msgstr "" @@ -1800,6 +1871,9 @@ msgstr "Robot renifleur volant" msgid "Withdraw shield (\\key action;)" msgstr "Refermer le bouclier (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + msgid "Worm" msgstr "Ver" @@ -1948,6 +2022,9 @@ msgstr "\\Drapeaux violets" msgid "\\Yellow flags" msgstr "\\Drapeaux jaunes" +msgid "by" +msgstr "" + msgid "colobot.info" msgstr "colobot.info" diff --git a/po/pl.po b/po/pl.po index 92c209a6..5e3daeae 100644 --- a/po/pl.po +++ b/po/pl.po @@ -127,6 +127,9 @@ msgstr "Wygląd\\Wybierz swoją postać" msgid "Apply changes\\Activates the changed settings" msgstr "Zastosuj zmiany\\Aktywuje zmienione ustawienia" +msgid "Apply\\Apply the current mod configuration" +msgstr "Zastosuj\\Zastosuj obecną konfigurację modów" + msgid "Appropriate constructor missing" msgstr "Brak odpowiedniego konstruktora" @@ -373,6 +376,9 @@ msgstr "Zmień kamerę\\Przełącza pomiędzy kamerą pokładową i śledzącą" msgid "Change player\\Change player" msgstr "Zmień gracza\\Zmień gracza" +msgid "Changes" +msgstr "Zmiany" + msgid "Chapters:" msgstr "Rozdziały:" @@ -442,6 +448,12 @@ msgstr "Kopiuj" msgid "Copy (Ctrl+C)" msgstr "Kopiuj (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "Nie udało się otworzyć przeglądarki plików!" + +msgid "Could not open the web browser!" +msgstr "Nie udało się otworzyć przeglądarki internetowej!" + msgid "Current mission saved" msgstr "Bieżąca misja zapisana" @@ -475,6 +487,9 @@ msgstr "Kopalnia" msgid "Descend\\Reduces the power of the jet" msgstr "W dół\\Zmniejsza moc silnika" +msgid "Description:" +msgstr "Opis:" + msgid "Destroy" msgstr "Zniszcz" @@ -487,6 +502,9 @@ msgstr "Destroyer" msgid "Device\\Driver and resolution settings" msgstr "Urządzenie\\Ustawienia sterownika i rozdzielczości" +msgid "Disable\\Disable the selected mod" +msgstr "Wyłącz\\Wyłącza zaznaczonego moda" + msgid "Dividing by zero" msgstr "Dzielenie przez zero" @@ -503,6 +521,9 @@ msgstr "Drzwi zablokowane przez robota lub inny obiekt" msgid "Down (\\key gdown;)" msgstr "Dół (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "W dół\\Przenieś zaznaczonego moda w dół, aby był załadowany później (mody mogą nadpisywać pliki modów wyżej)" + msgid "Drawer bot" msgstr "Robot rysownik" @@ -530,6 +551,9 @@ msgstr "Jajo" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "Włącz\\Włącza zaznaczonego moda" + msgid "End of block missing" msgstr "Brak końca bloku" @@ -756,6 +780,9 @@ msgstr "Zainfekowane wirusem, chwilowo niesprawne" msgid "Information exchange post" msgstr "Stacja przekaźnikowa informacji" +msgid "Information:" +msgstr "Informacje:" + msgid "Instruction \"break\" outside a loop" msgstr "Polecenie \"break\" na zewnątrz pętli" @@ -909,6 +936,15 @@ msgstr "Misje+" msgid "Missions\\Select mission" msgstr "Misje\\Wybierz misję" +msgid "Mods" +msgstr "Mody" + +msgid "Mods:" +msgstr "Mody:" + +msgid "Mods\\Mod manager" +msgstr "Mody\\Zarządzanie modami" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Odwrócenie myszy X\\Odwrócenie kierunków przewijania w poziomie" @@ -957,6 +993,12 @@ msgstr "Następny obiekt\\Zaznacza następny obiekt" msgid "No" msgstr "Nie" +msgid "No changes." +msgstr "Brak zmian." + +msgid "No description." +msgstr "Brak opisu." + msgid "No energy in the subsoil" msgstr "Brak energii w ziemi" @@ -1077,6 +1119,9 @@ msgstr "Otwórz" msgid "Open (Ctrl+O)" msgstr "Otwórz (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "Otwórz katalog\\Otwórz katalog z modami" + msgid "Opening brace missing" msgstr "Brak klamry otwierającej" @@ -1287,6 +1332,9 @@ msgstr "Czerwona flaga" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Odbicia na przyciskach \\Świecące przyciski" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "Odśwież\\Odśwież listę obecnie zainstalowanych modów" + msgid "Remains of Apollo mission" msgstr "Pozostałości z misji Apollo" @@ -1557,6 +1605,10 @@ msgstr "Filtrowanie tekstur\\Filtrowanie tekstur" msgid "Textures" msgstr "Tekstury" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "Nie udało się otworzyć adresu %s w przeglądarce internetowej." + msgid "The battle has ended" msgstr "Bitwa zakończyła się" @@ -1569,9 +1621,16 @@ msgstr "Funkcja nie zwróciła żadnej wartości" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "Misja nie jest wypełniona (naciśnij \\key help; aby uzyskać szczegóły)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "Nie udało się otworzyć ścieżki %s w przeglądarce plików." + msgid "The types of the two operands are incompatible" msgstr "Niezgodne typy operatorów" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "Są niezapisane zmiany. Czy chcesz je zapisać przed wyjściem?" + msgid "This class already exists" msgstr "Taka klasa już istnieje" @@ -1696,6 +1755,9 @@ msgstr "Jednostka" msgid "Unknown Object" msgstr "Obiekt nieznany" +msgid "Unknown author" +msgstr "Nieznany autor" + msgid "Unknown command" msgstr "Nieznane polecenie" @@ -1708,6 +1770,9 @@ msgstr "Funkcja nieznana" msgid "Up (\\key gup;)" msgstr "Góra (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "W górę\\Przenieś zaznaczonego moda w górę, aby był załadowany wcześniej (mody mogą nadpisywać pliki modów wyżej)" + msgid "Uranium deposit (site for derrick)" msgstr "Złoże uranu (miejsce na kopalnię)" @@ -1729,6 +1794,9 @@ msgstr "Zmienna nie została zainicjalizowana" msgid "Vault" msgstr "Skrytka" +msgid "Version" +msgstr "Wersja" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "Synchronizacja pionowa\\Ogranicza ilość klatek na sekundę do wartości odświeżania ekranu" @@ -1747,6 +1815,9 @@ msgstr "Osa śmiertelnie raniona" msgid "Waste" msgstr "Odpady" +msgid "Website" +msgstr "Strona internetowa" + msgid "Wheeled builder" msgstr "" @@ -1780,6 +1851,9 @@ msgstr "Szperacz latający" msgid "Withdraw shield (\\key action;)" msgstr "Wyłącz osłonę (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "Warsztat\\Otwórz warsztat, aby poszukać modów" + msgid "Worm" msgstr "Robal" @@ -1930,6 +2004,9 @@ msgstr "\\Fioletowe flagi" msgid "\\Yellow flags" msgstr "\\Żółte flagi" +msgid "by" +msgstr "autorstwa" + msgid "colobot.info" msgstr "colobot.info" diff --git a/po/pt.po b/po/pt.po index ebef8681..8c152823 100644 --- a/po/pt.po +++ b/po/pt.po @@ -125,6 +125,9 @@ msgstr "Aparência\\Escolha sua aparência" msgid "Apply changes\\Activates the changed settings" msgstr "Aplicar mudanças\\Ativa as configurações alteradas" +msgid "Apply\\Apply the current mod configuration" +msgstr "" + msgid "Appropriate constructor missing" msgstr "Construtor apropriado faltando" @@ -371,6 +374,9 @@ msgstr "Mudar câmera\\Alterna entre câmera incorporada e câmera seguidora" msgid "Change player\\Change player" msgstr "Mudar jogador\\Mudar jogador" +msgid "Changes" +msgstr "" + msgid "Chapters:" msgstr "Capítulos:" @@ -441,6 +447,12 @@ msgstr "Copiar" msgid "Copy (Ctrl+C)" msgstr "Copiar (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + msgid "Current mission saved" msgstr "Missão atual salva" @@ -474,6 +486,9 @@ msgstr "Extrator" msgid "Descend\\Reduces the power of the jet" msgstr "Descer\\Diminui o poder do jato" +msgid "Description:" +msgstr "" + msgid "Destroy" msgstr "Destruir" @@ -486,6 +501,9 @@ msgstr "Destruidor" msgid "Device\\Driver and resolution settings" msgstr "Dispositivo\\Configurações de driver e resolução" +msgid "Disable\\Disable the selected mod" +msgstr "" + msgid "Dividing by zero" msgstr "Dividindo por zero" @@ -502,6 +520,9 @@ msgstr "Portas bloqueadas por um robô ou outro objeto" msgid "Down (\\key gdown;)" msgstr "Baixo (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Drawer bot" msgstr "Robô cartoonista" @@ -529,6 +550,9 @@ msgstr "Ovo" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "" + msgid "End of block missing" msgstr "Fim do bloco ausente" @@ -756,6 +780,9 @@ msgstr "Infectado por vírus; temporariamento fora de serviço" msgid "Information exchange post" msgstr "Posto de troca de informação" +msgid "Information:" +msgstr "" + msgid "Instruction \"break\" outside a loop" msgstr "Intrução \"break\" fora de um laço" @@ -923,6 +950,15 @@ msgstr "Missões+" msgid "Missions\\Select mission" msgstr "Missões\\Selecione uma missão" +msgid "Mods" +msgstr "" + +msgid "Mods:" +msgstr "" + +msgid "Mods\\Mod manager" +msgstr "" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Inversão de mouse X\\Inverte a direção da rolagem no eixo X" @@ -971,6 +1007,12 @@ msgstr "Próximo objeto\\Selecionar o próximo objeto" msgid "No" msgstr "Não" +msgid "No changes." +msgstr "" + +msgid "No description." +msgstr "" + msgid "No energy in the subsoil" msgstr "Nenhuma energia no subsolo" @@ -1091,6 +1133,9 @@ msgstr "Abrir" msgid "Open (Ctrl+O)" msgstr "Abrir (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "" + msgid "Opening brace missing" msgstr "Chave de abertura ausente" @@ -1302,6 +1347,9 @@ msgstr "Bandeira vermelha" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Reflexões nos botões\\Botões brilhantes" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Remains of Apollo mission" msgstr "Restos da missão Apollo" @@ -1572,6 +1620,10 @@ msgstr "Filtragem de textura\\Filtragem de textura" msgid "Textures" msgstr "Texturas" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + msgid "The battle has ended" msgstr "A batalha acabou" @@ -1584,9 +1636,16 @@ msgstr "A função não retornou nenhum valor" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "A missão não foi completada ainda (pressione \\key help; para mais detalhes)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + msgid "The types of the two operands are incompatible" msgstr "Os tipos dos dois operandos são incompativeis" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "This class already exists" msgstr "Esta classe já existe" @@ -1711,6 +1770,9 @@ msgstr "Unidade" msgid "Unknown Object" msgstr "Objeto desconhecido" +msgid "Unknown author" +msgstr "" + msgid "Unknown command" msgstr "Comando desconhecido" @@ -1723,6 +1785,9 @@ msgstr "Função desconhecida" msgid "Up (\\key gup;)" msgstr "Cima (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Uranium deposit (site for derrick)" msgstr "Depósito de urânio (local para extrator)" @@ -1744,6 +1809,9 @@ msgstr "Variável não inicializada" msgid "Vault" msgstr "Cofre" +msgid "Version" +msgstr "" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "" @@ -1762,6 +1830,9 @@ msgstr "Vespa fatalmente ferida" msgid "Waste" msgstr "Desperdício" +msgid "Website" +msgstr "" + msgid "Wheeled builder" msgstr "" @@ -1795,6 +1866,9 @@ msgstr "Farejador alado" msgid "Withdraw shield (\\key action;)" msgstr "Retirar escudo (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + msgid "Worm" msgstr "Verme" @@ -1943,6 +2017,9 @@ msgstr "\\Bandeiras violetas" msgid "\\Yellow flags" msgstr "\\Bandeiras amarelas" +msgid "by" +msgstr "" + msgid "colobot.info" msgstr "colobot.info" diff --git a/po/ru.po b/po/ru.po index a0f0d658..09d3d571 100644 --- a/po/ru.po +++ b/po/ru.po @@ -127,6 +127,9 @@ msgstr "Внешность\\Настройка внешности" msgid "Apply changes\\Activates the changed settings" msgstr "Принять\\Принять изменения настроек" +msgid "Apply\\Apply the current mod configuration" +msgstr "" + msgid "Appropriate constructor missing" msgstr "Соответствующий конструктор отсутствует" @@ -378,6 +381,9 @@ msgstr "Изменить вид\\Переключение между борто msgid "Change player\\Change player" msgstr "Новый игрок\\Выберите имя для игрока" +msgid "Changes" +msgstr "" + msgid "Chapters:" msgstr "Разделы:" @@ -449,6 +455,12 @@ msgstr "Копировать" msgid "Copy (Ctrl+C)" msgstr "Копировать (Ctrl+C)" +msgid "Could not open the file explorer!" +msgstr "" + +msgid "Could not open the web browser!" +msgstr "" + msgid "Current mission saved" msgstr "Текущая миссия сохранена" @@ -483,6 +495,9 @@ msgstr "Космический корабль" msgid "Descend\\Reduces the power of the jet" msgstr "Снижение и посадка\\Понижение мощности реактивного двигателя" +msgid "Description:" +msgstr "" + msgid "Destroy" msgstr "Уничтожить" @@ -495,6 +510,9 @@ msgstr "Уничтожитель" msgid "Device\\Driver and resolution settings" msgstr "Устройство\\Драйвер и настройки разрешения" +msgid "Disable\\Disable the selected mod" +msgstr "" + msgid "Dividing by zero" msgstr "Деление на ноль (запрещено!)" @@ -511,6 +529,9 @@ msgstr "Двери заблокированы роботом или другим msgid "Down (\\key gdown;)" msgstr "Вниз (\\key gdown;)" +msgid "Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Drawer bot" msgstr "Рисовальщик" @@ -538,6 +559,9 @@ msgstr "Яйцо" msgid "Empty character constant" msgstr "" +msgid "Enable\\Enable the selected mod" +msgstr "" + msgid "End of block missing" msgstr "Отсутствует конец блока" @@ -765,6 +789,9 @@ msgstr "Заражено вирусом. Временно вышел из стр msgid "Information exchange post" msgstr "Пост обмена информацией" +msgid "Information:" +msgstr "" + msgid "Instruction \"break\" outside a loop" msgstr "Инструкция \"break\" вне цикла" @@ -932,6 +959,15 @@ msgstr "Миссии+" msgid "Missions\\Select mission" msgstr "Миссии\\Выбор миссии" +msgid "Mods" +msgstr "" + +msgid "Mods:" +msgstr "" + +msgid "Mods\\Mod manager" +msgstr "" + msgid "Mouse inversion X\\Inversion of the scrolling direction on the X axis" msgstr "Инверсия мыши по оси X\\Инверсия прокрутки по оси Х" @@ -982,6 +1018,12 @@ msgstr "Следующий объект\\Выбор следующего объ msgid "No" msgstr "Нет" +msgid "No changes." +msgstr "" + +msgid "No description." +msgstr "" + msgid "No energy in the subsoil" msgstr "Под землей нет запасов энергии" @@ -1102,6 +1144,9 @@ msgstr "Открыть" msgid "Open (Ctrl+O)" msgstr "Открыть (Ctrl+O)" +msgid "Open Directory\\Open the mods directory" +msgstr "" + msgid "Opening brace missing" msgstr "Открывающая скобка отсутствует" @@ -1314,6 +1359,9 @@ msgstr "Красный флаг" msgid "Reflections on the buttons \\Shiny buttons" msgstr "Отражения на кнопках \\Блестящие кнопки" +msgid "Refresh\\Refresh the list of currently installed mods" +msgstr "" + msgid "Remains of Apollo mission" msgstr "Остатки миссии Аполлон" @@ -1588,6 +1636,10 @@ msgstr "Фильтрация текстур\\Фильтрация текстур msgid "Textures" msgstr "Текстуры" +#, c-format +msgid "The address %s could not be opened in a web browser." +msgstr "" + msgid "The battle has ended" msgstr "" @@ -1600,9 +1652,16 @@ msgstr "Функция не возвратила значения" msgid "The mission is not accomplished yet (press \\key help; for more details)" msgstr "Миссия еще не выполнена (нажмите \\key help; для более подробной информации)" +#, c-format +msgid "The path %s could not be opened in a file explorer." +msgstr "" + msgid "The types of the two operands are incompatible" msgstr "Типы операндов несовместимы" +msgid "There are unsaved changes. Do you want to save them before leaving?" +msgstr "" + msgid "This class already exists" msgstr "Этот класс уже существует" @@ -1727,6 +1786,9 @@ msgstr "Юнит" msgid "Unknown Object" msgstr "Неизвестный объект" +msgid "Unknown author" +msgstr "" + msgid "Unknown command" msgstr "Неизвестная команда" @@ -1739,6 +1801,9 @@ msgstr "Неизвестная функция" msgid "Up (\\key gup;)" msgstr "Вверх (\\key gup;)" +msgid "Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)" +msgstr "" + msgid "Uranium deposit (site for derrick)" msgstr "Запасы урана (место для буровой вышки)" @@ -1760,6 +1825,9 @@ msgstr "Переменная не инициализирована" msgid "Vault" msgstr "Хранилище" +msgid "Version" +msgstr "" + msgid "Vertical Synchronization\\Limits the number of frames per second to display frequency" msgstr "" @@ -1778,6 +1846,9 @@ msgstr "Оса смертельно ранена" msgid "Waste" msgstr "Мусор" +msgid "Website" +msgstr "" + msgid "Wheeled builder" msgstr "" @@ -1811,6 +1882,9 @@ msgstr "Летающий искатель" msgid "Withdraw shield (\\key action;)" msgstr "Снять щит (\\key action;)" +msgid "Workshop\\Open the workshop to search for mods" +msgstr "" + msgid "Worm" msgstr "Червь" @@ -1959,6 +2033,9 @@ msgstr "\\Фиолетовый флаг" msgid "\\Yellow flags" msgstr "\\Желтый флаг" +msgid "by" +msgstr "" + msgid "colobot.info" msgstr "colobot.info" diff --git a/src/CBot/stdlib/MathFunctions.cpp b/src/CBot/stdlib/MathFunctions.cpp index b51d5748..f6e5b484 100644 --- a/src/CBot/stdlib/MathFunctions.cpp +++ b/src/CBot/stdlib/MathFunctions.cpp @@ -144,13 +144,36 @@ bool rRand(CBotVar* var, CBotVar* result, int& exception, void* user) bool rAbs(CBotVar* var, CBotVar* result, int& exception, void* user) { - float value; + switch (result->GetType()) + { + case CBotTypDouble: + *result = fabs(var->GetValDouble()); + break; + case CBotTypFloat: + *result = fabs(var->GetValFloat()); + break; + case CBotTypLong: + *result = labs(var->GetValLong()); + break; + default: + *result = abs(var->GetValInt()); + break; + } - value = var->GetValFloat(); - result->SetValFloat(fabs(value)); return true; } +CBotTypResult cAbs(CBotVar* &var, void* user) +{ + if ( var == nullptr ) return CBotTypResult(CBotErrLowParam); + if ( var->GetType() > CBotTypDouble ) return CBotTypResult(CBotErrBadNum); + + CBotTypResult returnType(var->GetType()); + var = var->GetNext(); + if ( var != nullptr ) return CBotTypResult(CBotErrOverParam); + return returnType; +} + // Instruction "floor()" bool rFloor(CBotVar* var, CBotVar* result, int& exception, void* user) @@ -209,7 +232,7 @@ void InitMathFunctions() CBotProgram::AddFunction("sqrt", rSqrt, cOneFloat); CBotProgram::AddFunction("pow", rPow, cTwoFloat); CBotProgram::AddFunction("rand", rRand, cNull); - CBotProgram::AddFunction("abs", rAbs, cOneFloat); + CBotProgram::AddFunction("abs", rAbs, cAbs); CBotProgram::AddFunction("floor", rFloor, cOneFloat); CBotProgram::AddFunction("ceil", rCeil, cOneFloat); CBotProgram::AddFunction("round", rRound, cOneFloat); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aefd0fad..3c5f4063 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,25 +48,30 @@ elseif(PLATFORM_WINDOWS) if(${MSVC_STATIC}) if (${OPENAL_SOUND}) - find_library(FLAC_LIBRARY NAMES flac.lib) - find_library(VORBIS_LIBRARY NAMES vorbis.lib) - find_library(VORBISENC_LIBRARY NAMES vorbisenc.lib) - find_library(OGG_LIBRARY NAMES ogg.lib) + find_library(FLAC_LIBRARY NAMES flac) + find_library(VORBIS_LIBRARY NAMES vorbis) + find_library(VORBISENC_LIBRARY NAMES vorbisenc) + find_library(OGG_LIBRARY NAMES ogg) + find_library(OPUS_LIBRARY NAMES opus) set(OPENAL_MSVC_LIBS ${FLAC_LIBRARY} ${VORBIS_LIBRARY} ${VORBISENC_LIBRARY} ${OGG_LIBRARY} + ${OPUS_LIBRARY} ) endif() - find_library(BZ2_LIBRARY NAMES bz2.lib) - find_library(JPEG_LIBRARY NAMES jpeg.lib) - find_library(TIFF_LIBRARY NAMES tiff.lib) - find_library(LZMA_LIBRARY NAMES lzma.lib) - find_library(FREETYPE_LIBRARY NAMES freetype.lib) - find_library(ICONV_LIBRARY NAMES libiconv.lib) - find_library(CHARSET_LIBRARY NAMES libcharset.lib) + find_library(BZ2_LIBRARY NAMES bz2) + find_library(JPEG_LIBRARY NAMES jpeg) + find_library(TIFF_LIBRARY NAMES tiff) + find_library(LZMA_LIBRARY NAMES lzma) + find_library(FREETYPE_LIBRARY NAMES freetype) + find_library(ICONV_LIBRARY NAMES iconv) + find_library(CHARSET_LIBRARY NAMES charset) + find_library(BROTLICOMMON_LIBRARY NAMES brotlicommon-static) + find_library(BROTLIENC_LIBRARY NAMES brotlienc-static) + find_library(BROTLIDEC_LIBRARY NAMES brotlidec-static) set(MSVC_LIBS ${LIBINTL_LIBRARY} ${OPENAL_MSVC_LIBS} @@ -77,6 +82,9 @@ elseif(PLATFORM_WINDOWS) ${FREETYPE_LIBRARY} ${ICONV_LIBRARY} ${CHARSET_LIBRARY} + ${BROTLICOMMON_LIBRARY} + ${BROTLIENC_LIBRARY} + ${BROTLIDEC_LIBRARY} winmm.lib dxguid.lib imm32.lib @@ -145,6 +153,8 @@ set(BASE_SOURCES app/controller.h app/input.cpp app/input.h + app/modman.cpp + app/modman.h app/pathman.cpp app/pathman.h app/pausemanager.cpp @@ -418,6 +428,7 @@ set(BASE_SOURCES object/object_interface_type.h object/object_manager.cpp object/object_manager.h + object/object_type.cpp object/object_type.h object/old_object.cpp object/old_object.h @@ -567,6 +578,8 @@ set(BASE_SOURCES ui/screen/screen_loading.h ui/screen/screen_main_menu.cpp ui/screen/screen_main_menu.h + ui/screen/screen_mod_list.cpp + ui/screen/screen_mod_list.h ui/screen/screen_player_select.cpp ui/screen/screen_player_select.h ui/screen/screen_quit.cpp diff --git a/src/app/app.cpp b/src/app/app.cpp index b5bd3ca0..1e4515ed 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -21,6 +21,7 @@ #include "app/controller.h" #include "app/input.h" +#include "app/modman.h" #include "app/pathman.h" #include "common/config_file.h" @@ -113,7 +114,8 @@ CApplication::CApplication(CSystemUtils* systemUtils) m_private(MakeUnique()), m_configFile(MakeUnique()), m_input(MakeUnique()), - m_pathManager(MakeUnique(systemUtils)) + m_pathManager(MakeUnique(systemUtils)), + m_modManager(MakeUnique(this, m_pathManager.get())) { m_exitCode = 0; m_active = false; @@ -220,6 +222,11 @@ CSoundInterface* CApplication::GetSound() return m_sound.get(); } +CModManager* CApplication::GetModManager() +{ + return m_modManager.get(); +} + void CApplication::LoadEnvironmentVariables() { auto dataDir = m_systemUtils->GetEnvVar("COLOBOT_DATA_DIR"); @@ -513,6 +520,10 @@ bool CApplication::Create() GetLogger()->Warn("Config could not be loaded. Default values will be used!\n"); } + m_modManager->FindMods(); + m_modManager->SaveMods(); + m_modManager->MountAllMods(); + // Create the sound instance. #ifdef OPENAL_SOUND if (!m_headless) @@ -538,6 +549,8 @@ bool CApplication::Create() /* SDL initialization sequence */ + // Creating the m_engine now because it holds the vsync flag + m_engine = MakeUnique(this, m_systemUtils); Uint32 initFlags = SDL_INIT_VIDEO | SDL_INIT_TIMER; @@ -682,8 +695,6 @@ bool CApplication::Create() } // Create the 3D engine - m_engine = MakeUnique(this, m_systemUtils); - m_engine->SetDevice(m_device.get()); if (! m_engine->Create() ) @@ -698,21 +709,7 @@ bool CApplication::Create() // Create the robot application. m_controller = MakeUnique(); - CThread musicLoadThread([this]() - { - GetLogger()->Debug("Cache sounds...\n"); - SystemTimeStamp* musicLoadStart = m_systemUtils->CreateTimeStamp(); - m_systemUtils->GetCurrentTimeStamp(musicLoadStart); - - m_sound->CacheAll(); - - SystemTimeStamp* musicLoadEnd = m_systemUtils->CreateTimeStamp(); - m_systemUtils->GetCurrentTimeStamp(musicLoadEnd); - float musicLoadTime = m_systemUtils->TimeStampDiff(musicLoadStart, musicLoadEnd, STU_MSEC); - GetLogger()->Debug("Sound loading took %.2f ms\n", musicLoadTime); - }, - "Sound loading thread"); - musicLoadThread.Start(); + StartLoadingMusic(); if (m_runSceneCategory == LevelCategory::Max) m_controller->StartApp(); @@ -726,6 +723,15 @@ bool CApplication::Create() return true; } +void CApplication::ReloadResources() +{ + GetLogger()->Info("Reloading resources\n"); + m_engine->ReloadAllTextures(); + StartLoadingMusic(); + m_controller->GetRobotMain()->UpdateCustomLevelList(); +} + + bool CApplication::CreateVideoSurface() { Uint32 videoFlags = SDL_WINDOW_OPENGL; @@ -844,24 +850,9 @@ bool CApplication::CreateVideoSurface() int vsync = 0; if (GetConfigFile().GetIntProperty("Setup", "VSync", vsync)) { - while (SDL_GL_SetSwapInterval(vsync) == -1) - { - switch(vsync) - { - case -1: //failed with adaptive sync? - GetLogger()->Warn("Adaptive sync not supported.\n"); - vsync = 1; - break; - case 1: //failed with VSync enabled? - GetLogger()->Warn("Couldn't enable VSync.\n"); - vsync = 0; - break; - case 0: //failed with VSync disabled? - GetLogger()->Warn("Couldn't disable VSync.\n"); - vsync = 1; - break; - } - } + m_engine->SetVSync(vsync); + TryToSetVSync(); + vsync = m_engine->GetVSync(); GetConfigFile().SetIntProperty("Setup", "VSync", vsync); GetLogger()->Info("Using Vsync: %s\n", (vsync == -1 ? "adaptive" : (vsync ? "true" : "false"))); @@ -870,6 +861,32 @@ bool CApplication::CreateVideoSurface() return true; } +void CApplication::TryToSetVSync() +{ + int vsync = m_engine->GetVSync(); + int result = SDL_GL_SetSwapInterval(vsync); + if (result == -1) + { + switch (vsync) + { + case -1: + GetLogger()->Warn("Adaptive sync not supported: %s\n", SDL_GetError()); + m_engine->SetVSync(1); + TryToSetVSync(); + break; + case 1: + GetLogger()->Warn("Couldn't enable VSync: %s\n", SDL_GetError()); + m_engine->SetVSync(0); + TryToSetVSync(); + break; + case 0: + GetLogger()->Warn("Couldn't disable VSync: %s\n", SDL_GetError()); + m_engine->SetVSync(SDL_GL_GetSwapInterval()); + break; + } + } +} + bool CApplication::ChangeVideoConfig(const Gfx::DeviceConfig &newConfig) { m_deviceConfig = newConfig; @@ -878,26 +895,7 @@ bool CApplication::ChangeVideoConfig(const Gfx::DeviceConfig &newConfig) SDL_SetWindowSize(m_private->window, m_deviceConfig.size.x, m_deviceConfig.size.y); SDL_SetWindowFullscreen(m_private->window, m_deviceConfig.fullScreen ? SDL_WINDOW_FULLSCREEN : 0); - int vsync = m_engine->GetVSync(); - while (SDL_GL_SetSwapInterval(vsync) == -1) - { - switch(vsync) - { - case -1: //failed with adaptive sync? - GetLogger()->Warn("Adaptive sync not supported.\n"); - vsync = 1; - break; - case 1: //failed with VSync enabled? - GetLogger()->Warn("Couldn't enable VSync.\n"); - vsync = 0; - break; - case 0: //failed with VSync disabled? - GetLogger()->Warn("Couldn't disable VSync.\n"); - vsync = 1; - break; - } - } - m_engine->SetVSync(vsync); + TryToSetVSync(); m_device->ConfigChanged(m_deviceConfig); @@ -1060,11 +1058,9 @@ int CApplication::Run() MoveMouse(Math::Point(0.5f, 0.5f)); // center mouse on start - SystemTimeStamp *lastLoopTimeStamp = m_systemUtils->CreateTimeStamp(); + SystemTimeStamp *previousTimeStamp = m_systemUtils->CreateTimeStamp(); SystemTimeStamp *currentTimeStamp = m_systemUtils->CreateTimeStamp(); SystemTimeStamp *interpolatedTimeStamp = m_systemUtils->CreateTimeStamp(); - m_systemUtils->GetCurrentTimeStamp(lastLoopTimeStamp); - m_systemUtils->CopyTimeStamp(currentTimeStamp, lastLoopTimeStamp); while (true) { @@ -1168,11 +1164,11 @@ int CApplication::Run() // If game speed is increased then we do extra ticks per loop iteration to improve physics accuracy. int numTickSlices = static_cast(GetSimulationSpeed()); if(numTickSlices < 1) numTickSlices = 1; - m_systemUtils->CopyTimeStamp(lastLoopTimeStamp, currentTimeStamp); + m_systemUtils->CopyTimeStamp(previousTimeStamp, m_curTimeStamp); m_systemUtils->GetCurrentTimeStamp(currentTimeStamp); for(int tickSlice = 0; tickSlice < numTickSlices; tickSlice++) { - m_systemUtils->InterpolateTimeStamp(interpolatedTimeStamp, lastLoopTimeStamp, currentTimeStamp, (tickSlice+1)/static_cast(numTickSlices)); + m_systemUtils->InterpolateTimeStamp(interpolatedTimeStamp, previousTimeStamp, currentTimeStamp, (tickSlice+1)/static_cast(numTickSlices)); Event event = CreateUpdateEvent(interpolatedTimeStamp); if (event.type != EVENT_NULL && m_controller != nullptr) { @@ -1203,7 +1199,7 @@ int CApplication::Run() } end: - m_systemUtils->DestroyTimeStamp(lastLoopTimeStamp); + m_systemUtils->DestroyTimeStamp(previousTimeStamp); m_systemUtils->DestroyTimeStamp(currentTimeStamp); m_systemUtils->DestroyTimeStamp(interpolatedTimeStamp); @@ -1539,6 +1535,26 @@ void CApplication::InternalResumeSimulation() m_absTimeBase = m_exactAbsTime; } +void CApplication::StartLoadingMusic() +{ + CThread musicLoadThread([this]() + { + GetLogger()->Debug("Cache sounds...\n"); + SystemTimeStamp* musicLoadStart = m_systemUtils->CreateTimeStamp(); + m_systemUtils->GetCurrentTimeStamp(musicLoadStart); + + m_sound->Reset(); + m_sound->CacheAll(); + + SystemTimeStamp* musicLoadEnd = m_systemUtils->CreateTimeStamp(); + m_systemUtils->GetCurrentTimeStamp(musicLoadEnd); + float musicLoadTime = m_systemUtils->TimeStampDiff(musicLoadStart, musicLoadEnd, STU_MSEC); + GetLogger()->Debug("Sound loading took %.2f ms\n", musicLoadTime); + }, + "Sound loading thread"); + musicLoadThread.Start(); +} + bool CApplication::GetSimulationSuspended() const { return m_simulationSuspended; diff --git a/src/app/app.h b/src/app/app.h index c55eca38..d238bde9 100644 --- a/src/app/app.h +++ b/src/app/app.h @@ -42,6 +42,7 @@ class CEventQueue; class CController; class CSoundInterface; class CInput; +class CModManager; class CPathManager; class CConfigFile; class CSystemUtils; @@ -162,6 +163,8 @@ public: CEventQueue* GetEventQueue(); //! Returns the sound subsystem CSoundInterface* GetSound(); + //! Returns the mod manager + CModManager* GetModManager(); public: //! Loads some data from environment variables @@ -170,6 +173,8 @@ public: ParseArgsStatus ParseArguments(int argc, char *argv[]); //! Initializes the application bool Create(); + //! Reloads the application resources, e.g. mods + void ReloadResources(); //! Main event loop int Run(); //! Returns the code to be returned at main() exit @@ -283,6 +288,9 @@ public: protected: //! Creates the window's SDL_Surface bool CreateVideoSurface(); + //! Tries to set the SDL vsync state desired by the 3D engine + //! The final state of SDL vsync is set in the 3D engine afterwards + void TryToSetVSync(); //! Processes the captured SDL event to Event struct Event ProcessSystemEvent(); @@ -301,6 +309,9 @@ protected: //! Internal procedure to reset time counters void InternalResumeSimulation(); + //! Loads music in a new thread + void StartLoadingMusic(); + protected: //! System utils instance CSystemUtils* m_systemUtils; @@ -322,6 +333,8 @@ protected: std::unique_ptr m_input; //! Path manager std::unique_ptr m_pathManager; + //! Mod manager + std::unique_ptr m_modManager; //! Code to return at exit int m_exitCode; diff --git a/src/app/modman.cpp b/src/app/modman.cpp new file mode 100644 index 00000000..42ba11be --- /dev/null +++ b/src/app/modman.cpp @@ -0,0 +1,358 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#include "app/modman.h" + +#include "common/config.h" + +#include "app/app.h" +#include "app/pathman.h" + +#include "common/config_file.h" +#include "common/logger.h" + +#include "common/resources/resourcemanager.h" + +#include "level/parser/parser.h" + +#include +#include +#include +#include + +using namespace boost::filesystem; + +CModManager::CModManager(CApplication* app, CPathManager* pathManager) + : m_app{app}, + m_pathManager{pathManager} +{ +} + +void CModManager::FindMods() +{ + m_mods.clear(); + m_userChanges = false; + + // Load names from the config file + std::vector savedModNames; + GetConfigFile().GetArrayProperty("Mods", "Names", savedModNames); + std::vector savedEnabled; + GetConfigFile().GetArrayProperty("Mods", "Enabled", savedEnabled); + + // Transform the data into Mod structures + m_mods.reserve(savedModNames.size()); + for (size_t i = 0; i < savedModNames.size(); ++i) + { + Mod mod{}; + mod.name = savedModNames[i]; + if (i < savedEnabled.size()) + { + mod.enabled = savedEnabled[i]; + } + mod.path = ""; // Find the path later + m_mods.push_back(mod); + } + + // Search the folders for mods + auto rawPaths = m_pathManager->FindMods(); + std::map modPaths; + for (const auto& path : rawPaths) + { + auto modName = boost::filesystem::path(path).stem().string(); + modPaths.insert(std::make_pair(modName, path)); + } + + // Find paths for already saved mods + auto it = m_mods.begin(); + while (it != m_mods.end()) + { + auto& mod = *it; + const auto pathsIt = modPaths.find(mod.name); + if (pathsIt != modPaths.end()) + { + mod.path = (*pathsIt).second; + modPaths.erase(pathsIt); + ++it; + } + else + { + GetLogger()->Warn("Could not find mod %s, removing it from the list\n", mod.name.c_str()); + it = m_mods.erase(it); + } + } + + // Add the remaining found mods to the end of the list + for (const auto& newMod : modPaths) + { + Mod mod{}; + mod.name = newMod.first; + mod.path = newMod.second; + m_mods.push_back(mod); + } + + // Load the metadata for each mod + + // Unfortunately, the paths are distinguished by their real paths, not mount points + // So we must unmount mods temporarily + for (const auto& path : m_mountedModPaths) + { + UnmountMod(path); + } + + for (auto& mod : m_mods) + { + MountMod(mod, "/temp/mod"); + LoadModData(mod); + UnmountMod(mod); + } + + // Mount back + for (const auto& path : m_mountedModPaths) + { + MountMod(path); + } +} + +void CModManager::ReloadMods() +{ + UnmountAllMountedMods(); + MountAllMods(); + ReloadResources(); +} + +void CModManager::EnableMod(size_t i) +{ + m_mods[i].enabled = true; + m_userChanges = true; +} + +void CModManager::DisableMod(size_t i) +{ + m_mods[i].enabled = false; + m_userChanges = true; +} + +size_t CModManager::MoveUp(size_t i) +{ + if (i != 0) + { + std::swap(m_mods[i - 1], m_mods[i]); + m_userChanges = true; + return i - 1; + } + else + { + return i; + } +} + +size_t CModManager::MoveDown(size_t i) +{ + if (i != m_mods.size() - 1) + { + std::swap(m_mods[i], m_mods[i + 1]); + m_userChanges = true; + return i + 1; + } + else + { + return i; + } +} + +bool CModManager::Changes() +{ + std::vector paths; + for (const auto& mod : m_mods) + { + if (mod.enabled) + { + paths.push_back(mod.path); + } + } + return paths != m_mountedModPaths || m_userChanges; +} + +void CModManager::MountAllMods() +{ + for (const auto& mod : m_mods) + { + if (mod.enabled) + { + MountMod(mod); + m_mountedModPaths.push_back(mod.path); + } + } +} + +void CModManager::ReloadResources() +{ + m_app->ReloadResources(); +} + +void CModManager::SaveMods() +{ + std::vector savedNames; + savedNames.reserve(m_mods.size()); + std::transform(m_mods.begin(), m_mods.end(), std::back_inserter(savedNames), [](const Mod& mod) { return mod.name; }); + GetConfigFile().SetArrayProperty("Mods", "Names", savedNames); + + std::vector savedEnabled; + savedEnabled.reserve(m_mods.size()); + std::transform(m_mods.begin(), m_mods.end(), std::back_inserter(savedEnabled), [](const Mod& mod) { return mod.enabled; }); + GetConfigFile().SetArrayProperty("Mods", "Enabled", savedEnabled); + + GetConfigFile().Save(); + + m_userChanges = false; +} + +size_t CModManager::CountMods() const +{ + return m_mods.size(); +} + +const Mod& CModManager::GetMod(size_t i) const +{ + return m_mods[i]; +} + +const std::vector& CModManager::GetMods() const +{ + return m_mods; +} + +void CModManager::LoadModData(Mod& mod) +{ + auto& data = mod.data; + + data.displayName = mod.name; + + try + { + CLevelParser levelParser("temp/mod/manifest.txt"); + if (levelParser.Exists()) + { + levelParser.Load(); + + CLevelParserLine* line = nullptr; + + // DisplayName + line = levelParser.GetIfDefined("DisplayName"); + if (line != nullptr && line->GetParam("text")->IsDefined()) + { + data.displayName = line->GetParam("text")->AsString(); + } + + // Author + line = levelParser.GetIfDefined("Author"); + if (line != nullptr && line->GetParam("text")->IsDefined()) + { + data.author = line->GetParam("text")->AsString(); + } + + // Version + line = levelParser.GetIfDefined("Version"); + if (line != nullptr) + { + if (line->GetParam("text")->IsDefined()) + { + data.version = line->GetParam("text")->AsString(); + } + else if (line->GetParam("major")->IsDefined() && line->GetParam("minor")->IsDefined() && line->GetParam("patch")->IsDefined()) + { + auto major = boost::lexical_cast(line->GetParam("major")->AsInt()); + auto minor = boost::lexical_cast(line->GetParam("minor")->AsInt()); + auto patch = boost::lexical_cast(line->GetParam("patch")->AsInt()); + data.version = boost::algorithm::join(std::vector{ major, minor, patch }, "."); + } + } + + // Website + line = levelParser.GetIfDefined("Website"); + if (line != nullptr && line->GetParam("text")->IsDefined()) + { + data.website = line->GetParam("text")->AsString(); + } + + // Summary + line = levelParser.GetIfDefined("Summary"); + if (line != nullptr && line->GetParam("text")->IsDefined()) + { + data.summary = line->GetParam("text")->AsString(); + } + } + else + { + GetLogger()->Warn("No manifest file for mod %s\n", mod.name.c_str()); + } + } + catch (CLevelParserException& e) + { + GetLogger()->Warn("Failed parsing manifest for mod %s: %s\n", mod.name.c_str(), e.what()); + } + + // Changes + data.changes = CResourceManager::ListDirectories("temp/mod"); + auto levelsIt = std::find(data.changes.begin(), data.changes.end(), "levels"); + if (levelsIt != data.changes.end()) + { + auto levelsDirs = CResourceManager::ListDirectories("temp/mod/levels"); + if (!levelsDirs.empty()) + { + std::transform(levelsDirs.begin(), levelsDirs.end(), levelsDirs.begin(), [](const std::string& dir) { return "levels/" + dir; }); + levelsIt = data.changes.erase(levelsIt); + data.changes.insert(levelsIt, levelsDirs.begin(), levelsDirs.end()); + } + } +} + +void CModManager::MountMod(const Mod& mod, const std::string& mountPoint) +{ + MountMod(mod.path, mountPoint); +} + +void CModManager::MountMod(const std::string& path, const std::string& mountPoint) +{ + GetLogger()->Debug("Mounting mod: '%s' at path %s\n", path.c_str(), mountPoint.c_str()); + CResourceManager::AddLocation(path, true, mountPoint); +} + +void CModManager::UnmountMod(const Mod& mod) +{ + UnmountMod(mod.path); +} + +void CModManager::UnmountMod(const std::string& path) +{ + if (CResourceManager::LocationExists(path)) + { + GetLogger()->Debug("Unmounting mod: '%s'\n", path.c_str()); + CResourceManager::RemoveLocation(path); + } +} + +void CModManager::UnmountAllMountedMods() +{ + for (const auto& path : m_mountedModPaths) + { + UnmountMod(path); + } + m_mountedModPaths.clear(); +} diff --git a/src/app/modman.h b/src/app/modman.h new file mode 100644 index 00000000..09f8b0aa --- /dev/null +++ b/src/app/modman.h @@ -0,0 +1,124 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#pragma once + +#include +#include +#include + +class CApplication; +class CPathManager; + +struct ModData +{ + std::string displayName{}; + std::string author{}; + std::string version{}; + std::string website{}; + std::string summary{}; + std::vector changes{}; +}; + +struct Mod +{ + std::string name{}; + std::string path{}; + bool enabled = false; + ModData data{}; +}; + +/** + * \class CModManager + * \brief This class handles the list of mods. + * + * The order matters since the order in which files are loaded matters, + * because some files can be overwritten. + * + * The changes in the list do not immediately apply. + */ +class CModManager +{ +public: + CModManager(CApplication* app, CPathManager* pathManager); + + //! Finds all the mods along with their metadata + void FindMods(); + + //! Applies the current configuration and reloads the application + void ReloadMods(); + + //! Removes a mod from the list of loaded mods + void EnableMod(size_t i); + + //! Adds a mod to the list of loaded mods + void DisableMod(size_t i); + + //! Moves the selected mod up in the list so that it's loaded sooner than others, returns the new index + size_t MoveUp(size_t i); + + //! Moves the selected mod down in the list so that it's loaded later than others, returns the new index + size_t MoveDown(size_t i); + + //! Checks if the list of currently used mods differs from the current configuration or there were changes made by the user + bool Changes(); + + //! Saves the current configuration of mods to the config file + void SaveMods(); + + //! Number of mods loaded + size_t CountMods() const; + + //! Returns the reference to the mod in given position + const Mod& GetMod(size_t i) const; + + //! Returns the list of mods + const std::vector& GetMods() const; + +private: + // Allow access to MountAllMods() as CApplication doesn't want to reload itself during initialization + friend CApplication; + + //! Reloads application resources so the enabled mods are applied + void ReloadResources(); + + //! Load mod data into mod + void LoadModData(Mod& mod); + + //! Updates the paths in Path Manager according to the current mod configuration + void MountAllMods(); + + void MountMod(const Mod& mod, const std::string& mountPoint = ""); + void MountMod(const std::string& path, const std::string& mountPoint = ""); + void UnmountMod(const Mod& mod); + void UnmountMod(const std::string& path); + void UnmountAllMountedMods(); + +private: + CApplication* m_app; + CPathManager* m_pathManager; + + //! Paths to mods already in the virtual filesystem + std::vector m_mountedModPaths; + + //! List of mods + std::vector m_mods; + + bool m_userChanges = false; +}; diff --git a/src/app/pathman.cpp b/src/app/pathman.cpp index 690f6d34..e4c385dd 100644 --- a/src/app/pathman.cpp +++ b/src/app/pathman.cpp @@ -41,8 +41,7 @@ CPathManager::CPathManager(CSystemUtils* systemUtils) : m_dataPath(systemUtils->GetDataPath()) , m_langPath(systemUtils->GetLangPath()) , m_savePath(systemUtils->GetSaveDir()) - , m_modAutoloadDir{} - , m_mods{} + , m_modSearchDirs{} { } @@ -65,16 +64,6 @@ void CPathManager::SetSavePath(const std::string &savePath) m_savePath = savePath; } -void CPathManager::AddModAutoloadDir(const std::string &modAutoloadDirPath) -{ - m_modAutoloadDir.push_back(modAutoloadDirPath); -} - -void CPathManager::AddMod(const std::string &modPath) -{ - m_mods.push_back(modPath); -} - const std::string& CPathManager::GetDataPath() { return m_dataPath; @@ -131,40 +120,18 @@ void CPathManager::InitPaths() GetLogger()->Info("Data path: %s\n", m_dataPath.c_str()); GetLogger()->Info("Save path: %s\n", m_savePath.c_str()); - m_modAutoloadDir.push_back(m_dataPath + "/mods"); - m_modAutoloadDir.push_back(m_savePath + "/mods"); + m_modSearchDirs.push_back(m_dataPath + "/mods"); + m_modSearchDirs.push_back(m_savePath + "/mods"); - if (!m_modAutoloadDir.empty()) + if (!m_modSearchDirs.empty()) { - GetLogger()->Info("Mod autoload dirs:\n"); - for(const std::string& modAutoloadDir : m_modAutoloadDir) - GetLogger()->Info(" * %s\n", modAutoloadDir.c_str()); - } - if (!m_mods.empty()) - { - GetLogger()->Info("Mods:\n"); - for(const std::string& modPath : m_mods) - GetLogger()->Info(" * %s\n", modPath.c_str()); + GetLogger()->Info("Mod search dirs:\n"); + for(const std::string& modSearchDir : m_modSearchDirs) + GetLogger()->Info(" * %s\n", modSearchDir.c_str()); } CResourceManager::AddLocation(m_dataPath); - for (const std::string& modAutoloadDir : m_modAutoloadDir) - { - GetLogger()->Trace("Searching for mods in '%s'...\n", modAutoloadDir.c_str()); - for (const std::string& modPath : FindModsInDir(modAutoloadDir)) - { - GetLogger()->Info("Autoloading mod: '%s'\n", modPath.c_str()); - CResourceManager::AddLocation(modPath); - } - } - - for (const std::string& modPath : m_mods) - { - GetLogger()->Info("Loading mod: '%s'\n", modPath.c_str()); - CResourceManager::AddLocation(modPath); - } - CResourceManager::SetSaveLocation(m_savePath); CResourceManager::AddLocation(m_savePath); @@ -174,7 +141,45 @@ void CPathManager::InitPaths() GetLogger()->Debug(" * %s\n", path.c_str()); } -std::vector CPathManager::FindModsInDir(const std::string &dir) +void CPathManager::AddMod(const std::string &path) +{ + m_mods.push_back(path); +} + +std::vector CPathManager::FindMods() const +{ + std::vector mods; + GetLogger()->Info("Found mods:\n"); + for (const auto &searchPath : m_modSearchDirs) + { + for (const auto &modPath : FindModsInDir(searchPath)) + { + GetLogger()->Info(" * %s\n", modPath.c_str()); + mods.push_back(modPath); + } + } + GetLogger()->Info("Additional mod paths:\n"); + for (const auto& modPath : m_mods) + { + if (boost::filesystem::exists(modPath)) + { + GetLogger()->Info(" * %s\n", modPath.c_str()); + mods.push_back(modPath); + } + else + { + GetLogger()->Warn("Mod does not exist: %s\n", modPath.c_str()); + } + } + return mods; +} + +void CPathManager::AddModSearchDir(const std::string &modSearchDirPath) +{ + m_modSearchDirs.push_back(modSearchDirPath); +} + +std::vector CPathManager::FindModsInDir(const std::string &dir) const { std::vector ret; try diff --git a/src/app/pathman.h b/src/app/pathman.h index 561f66f3..d1ba4bbe 100644 --- a/src/app/pathman.h +++ b/src/app/pathman.h @@ -37,8 +37,6 @@ public: void SetDataPath(const std::string &dataPath); void SetLangPath(const std::string &langPath); void SetSavePath(const std::string &savePath); - void AddModAutoloadDir(const std::string &modAutoloadDirPath); - void AddMod(const std::string &modPath); const std::string& GetDataPath(); const std::string& GetLangPath(); @@ -49,9 +47,15 @@ public: //! Loads configured paths void InitPaths(); + //! Adds a path to a mod + void AddMod(const std::string& path); + //! Find paths to mods in mod search directories + std::vector FindMods() const; + //! Adds a mod search directory + void AddModSearchDir(const std::string &modSearchDirPath); + private: - //! Loads all mods from given directory - std::vector FindModsInDir(const std::string &dir); + std::vector FindModsInDir(const std::string &dir) const; private: //! Data path @@ -60,8 +64,8 @@ private: std::string m_langPath; //! Save path std::string m_savePath; - //! Mod autoload paths - std::vector m_modAutoloadDir; - //! Mod paths + //! Mod search paths + std::vector m_modSearchDirs; + //! Additional mod paths std::vector m_mods; }; diff --git a/src/common/config_file.h b/src/common/config_file.h index 6fbeae71..6e95f481 100644 --- a/src/common/config_file.h +++ b/src/common/config_file.h @@ -26,9 +26,15 @@ #include "common/singleton.h" +#include "common/logger.h" + #include +#include #include +#include +#include +#include /** @@ -100,6 +106,76 @@ public: */ bool GetBoolProperty(std::string section, std::string key, bool &value); + /** Gets an array of values of type T in section under specified key + * The value separator is ','. + * \a array will only be changed if key exists + * \return return true on success + */ + template + bool SetArrayProperty(std::string section, std::string key, const std::vector& array) + { + try + { + std::string convertedValue = ArrayToString(array); + m_propertyTree.put(section + "." + key, convertedValue); + m_needsSave = true; + } + catch (std::exception & e) + { + GetLogger()->Error("Error on editing config file: %s\n", e.what()); + return false; + } + return true; + } + + /** Sets an array of values of type T in section under specified key. + * The value separator is ','. + * \a array will only be changed if key exists + * \return return true on success + */ + template + bool GetArrayProperty(std::string section, std::string key, std::vector& array) + { + try + { + std::string readValue = m_propertyTree.get(section + "." + key); + std::vector convertedValue = StringToArray(readValue); + array = std::move(convertedValue); + } + catch (std::exception & e) + { + GetLogger()->Log(m_loaded ? LOG_INFO : LOG_TRACE, "Error on parsing config file: %s\n", e.what()); + return false; + } + return true; + } + +private: + template + std::vector StringToArray(const std::string& s) + { + std::vector result; + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, ',')) + { + result.push_back(boost::lexical_cast(item)); + } + return result; + } + + template + std::string ArrayToString(const std::vector &array) + { + std::ostringstream oss; + if (!array.empty()) + { + std::copy(array.begin(), array.end() - 1, std::ostream_iterator(oss, ",")); + oss << array.back(); + } + return oss.str(); + } + private: boost::property_tree::ptree m_propertyTree; bool m_needsSave; diff --git a/src/common/event.cpp b/src/common/event.cpp index 1a6ee43c..d75c8f9c 100644 --- a/src/common/event.cpp +++ b/src/common/event.cpp @@ -191,6 +191,7 @@ void InitializeEventTypeTexts() EVENT_TYPE_TEXT[EVENT_INTERFACE_USER] = "EVENT_INTERFACE_USER"; EVENT_TYPE_TEXT[EVENT_INTERFACE_SATCOM] = "EVENT_INTERFACE_SATCOM"; EVENT_TYPE_TEXT[EVENT_INTERFACE_PLUS] = "EVENT_INTERFACE_PLUS"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MODS] = "EVENT_INTERFACE_MODS"; EVENT_TYPE_TEXT[EVENT_INTERFACE_CHAP] = "EVENT_INTERFACE_CHAP"; EVENT_TYPE_TEXT[EVENT_INTERFACE_LIST] = "EVENT_INTERFACE_LIST"; @@ -277,6 +278,16 @@ void InitializeEventTypeTexts() EVENT_TYPE_TEXT[EVENT_INTERFACE_PLUS_RESEARCH] = "EVENT_INTERFACE_PLUS_RESEARCH"; EVENT_TYPE_TEXT[EVENT_INTERFACE_PLUS_EXPLORER] = "EVENT_INTERFACE_PLUS_EXPLORER"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MOD_LIST] = "EVENT_INTERFACE_MOD_LIST"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_WORKSHOP] = "EVENT_INTERFACE_WORKSHOP"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MODS_DIR] = "EVENT_INTERFACE_MODS_DIR"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE] = "EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MODS_APPLY] = "EVENT_INTERFACE_MODS_APPLY"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MOD_DETAILS] = "EVENT_INTERFACE_MOD_DETAILS"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MOD_MOVE_UP] = "EVENT_INTERFACE_MOD_MOVE_UP"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MOD_MOVE_DOWN] = "EVENT_INTERFACE_MOD_MOVE_DOWN"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_MODS_REFRESH] = "EVENT_INTERFACE_MODS_REFRESH"; + EVENT_TYPE_TEXT[EVENT_INTERFACE_GLINTl] = "EVENT_INTERFACE_GLINTl"; EVENT_TYPE_TEXT[EVENT_INTERFACE_GLINTr] = "EVENT_INTERFACE_GLINTr"; EVENT_TYPE_TEXT[EVENT_INTERFACE_GLINTu] = "EVENT_INTERFACE_GLINTu"; diff --git a/src/common/event.h b/src/common/event.h index 1bdb06e4..aec48ecb 100644 --- a/src/common/event.h +++ b/src/common/event.h @@ -226,6 +226,7 @@ enum EventType EVENT_INTERFACE_USER = 413, EVENT_INTERFACE_SATCOM = 414, EVENT_INTERFACE_PLUS = 415, + EVENT_INTERFACE_MODS = 416, EVENT_INTERFACE_CHAP = 420, EVENT_INTERFACE_LIST = 421, @@ -316,6 +317,17 @@ enum EventType EVENT_INTERFACE_PLUS_RESEARCH = 576, EVENT_INTERFACE_PLUS_EXPLORER = 577, + EVENT_INTERFACE_MOD_LIST = 580, + EVENT_INTERFACE_WORKSHOP = 581, + EVENT_INTERFACE_MODS_DIR = 582, + EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE = 583, + EVENT_INTERFACE_MODS_APPLY = 584, + EVENT_INTERFACE_MOD_SUMMARY = 585, + EVENT_INTERFACE_MOD_DETAILS = 586, + EVENT_INTERFACE_MOD_MOVE_UP = 587, + EVENT_INTERFACE_MOD_MOVE_DOWN = 888, + EVENT_INTERFACE_MODS_REFRESH = 589, + EVENT_INTERFACE_GLINTl = 590, EVENT_INTERFACE_GLINTr = 591, EVENT_INTERFACE_GLINTu = 592, diff --git a/src/common/resources/resourcemanager.cpp b/src/common/resources/resourcemanager.cpp index b51325b8..7c572483 100644 --- a/src/common/resources/resourcemanager.cpp +++ b/src/common/resources/resourcemanager.cpp @@ -62,9 +62,9 @@ std::string CResourceManager::CleanPath(const std::string& path) } -bool CResourceManager::AddLocation(const std::string &location, bool prepend) +bool CResourceManager::AddLocation(const std::string &location, bool prepend, const std::string &mountPoint) { - if (!PHYSFS_mount(location.c_str(), nullptr, prepend ? 0 : 1)) + if (!PHYSFS_mount(location.c_str(), mountPoint.c_str(), prepend ? 0 : 1)) { GetLogger()->Error("Error while mounting \"%s\": %s\n", location.c_str(), PHYSFS_getLastError()); return false; @@ -95,6 +95,12 @@ std::vector CResourceManager::GetLocations() return ret; } +bool CResourceManager::LocationExists(const std::string& location) +{ + auto locations = GetLocations(); + auto it = std::find(locations.cbegin(), locations.cend(), location); + return it != locations.cend(); +} bool CResourceManager::SetSaveLocation(const std::string &location) { diff --git a/src/common/resources/resourcemanager.h b/src/common/resources/resourcemanager.h index 8c09b91a..d15a6f02 100644 --- a/src/common/resources/resourcemanager.h +++ b/src/common/resources/resourcemanager.h @@ -36,11 +36,13 @@ public: static std::string CleanPath(const std::string &path); //! Add a location to the search path - static bool AddLocation(const std::string &location, bool prepend = true); + static bool AddLocation(const std::string &location, bool prepend = true, const std::string &mountPoint = ""); //! Remove a location from the search path static bool RemoveLocation(const std::string &location); //! List all locations in the search path static std::vector GetLocations(); + //! Check if given location is in the search path + static bool LocationExists(const std::string &location); static bool SetSaveLocation(const std::string &location); static std::string GetSaveLocation(); diff --git a/src/common/restext.cpp b/src/common/restext.cpp index a12aa67f..d8e16a00 100644 --- a/src/common/restext.cpp +++ b/src/common/restext.cpp @@ -71,13 +71,14 @@ void InitializeRestext() stringsText[RT_TITLE_MISSION] = TR("Missions"); stringsText[RT_TITLE_FREE] = TR("Free game"); stringsText[RT_TITLE_USER] = TR("User levels"); - stringsText[RT_TITLE_CODE_BATTLES]=TR("Code battles"); + stringsText[RT_TITLE_CODE_BATTLES] = TR("Code battles"); stringsText[RT_TITLE_SETUP] = TR("Options"); stringsText[RT_TITLE_NAME] = TR("Player's name"); stringsText[RT_TITLE_PERSO] = TR("Customize your appearance"); stringsText[RT_TITLE_WRITE] = TR("Save the current mission"); stringsText[RT_TITLE_READ] = TR("Load a saved mission"); stringsText[RT_TITLE_PLUS] = TR("Missions+"); + stringsText[RT_TITLE_MODS] = TR("Mods"); stringsText[RT_PLAY_CHAP_CHAPTERS] = TR("Chapters:"); stringsText[RT_PLAY_CHAP_PLANETS] = TR("Planets:"); @@ -109,6 +110,11 @@ void InitializeRestext() stringsText[RT_DIALOG_OK] = TR("OK"); stringsText[RT_DIALOG_NOUSRLVL_TITLE] = TR("No userlevels installed!"); stringsText[RT_DIALOG_NOUSRLVL_TEXT] = TR("This menu is for userlevels from mods, but you didn't install any"); + stringsText[RT_DIALOG_OPEN_PATH_FAILED_TITLE] = TR("Could not open the file explorer!"); + stringsText[RT_DIALOG_OPEN_PATH_FAILED_TEXT] = TR("The path %s could not be opened in a file explorer."); + stringsText[RT_DIALOG_OPEN_WEBSITE_FAILED_TITLE] = TR("Could not open the web browser!"); + stringsText[RT_DIALOG_OPEN_WEBSITE_FAILED_TEXT] = TR("The address %s could not be opened in a web browser."); + stringsText[RT_DIALOG_CHANGES_QUESTION] = TR("There are unsaved changes. Do you want to save them before leaving?"); stringsText[RT_STUDIO_LISTTT] = TR("Keyword help(\\key cbot;)"); stringsText[RT_STUDIO_COMPOK] = TR("Compilation ok (0 errors)"); @@ -154,7 +160,18 @@ void InitializeRestext() stringsText[RT_SCOREBOARD_RESULTS_TIME]= TR("Time: %s"); stringsText[RT_SCOREBOARD_RESULTS_LINE]= TR("%s: %d pts"); - + stringsText[RT_MOD_LIST] = TR("Mods:"); + stringsText[RT_MOD_DETAILS] = TR("Information:"); + stringsText[RT_MOD_SUMMARY] = TR("Description:"); + stringsText[RT_MOD_ENABLE] = TR("Enable\\Enable the selected mod"); + stringsText[RT_MOD_DISABLE] = TR("Disable\\Disable the selected mod"); + stringsText[RT_MOD_UNKNOWN_AUTHOR] = TR("Unknown author"); + stringsText[RT_MOD_AUTHOR_FIELD_NAME] = TR("by"); + stringsText[RT_MOD_VERSION_FIELD_NAME] = TR("Version"); + stringsText[RT_MOD_WEBSITE_FIELD_NAME] = TR("Website"); + stringsText[RT_MOD_CHANGES_FIELD_NAME] = TR("Changes"); + stringsText[RT_MOD_NO_SUMMARY] = TR("No description."); + stringsText[RT_MOD_NO_CHANGES] = TR("No changes."); stringsEvent[EVENT_LABEL_CODE_BATTLE] = TR("Code battle"); @@ -174,6 +191,7 @@ void InitializeRestext() stringsEvent[EVENT_INTERFACE_CODE_BATTLES] = TR("Code battles\\Program your robot to be the best of them all!"); stringsEvent[EVENT_INTERFACE_USER] = TR("Custom levels\\Levels from mods created by the users"); stringsEvent[EVENT_INTERFACE_SATCOM] = TR("SatCom"); + stringsEvent[EVENT_INTERFACE_MODS] = TR("Mods\\Mod manager"); stringsEvent[EVENT_INTERFACE_NAME] = TR("Change player\\Change player"); stringsEvent[EVENT_INTERFACE_SETUP] = TR("Options\\Preferences"); stringsEvent[EVENT_INTERFACE_AGAIN] = TR("Restart\\Restart the mission from the beginning"); @@ -184,6 +202,12 @@ void InitializeRestext() stringsEvent[EVENT_INTERFACE_BACK] = TR("<< Back \\Back to the previous screen"); stringsEvent[EVENT_INTERFACE_PLUS] = TR("+\\Missions with bonus content and optional challenges"); stringsEvent[EVENT_INTERFACE_PLAY] = TR("Play\\Start mission!"); + stringsEvent[EVENT_INTERFACE_WORKSHOP] = TR("Workshop\\Open the workshop to search for mods"); + stringsEvent[EVENT_INTERFACE_MODS_DIR] = TR("Open Directory\\Open the mods directory"); + stringsEvent[EVENT_INTERFACE_MODS_APPLY] = TR("Apply\\Apply the current mod configuration"); + stringsEvent[EVENT_INTERFACE_MOD_MOVE_UP] = TR("Up\\Move the selected mod up so it's loaded sooner (mods may overwrite files from the mods above them)"); + stringsEvent[EVENT_INTERFACE_MOD_MOVE_DOWN] = TR("Down\\Move the selected mod down so it's loaded later (mods may overwrite files from the mods above them)"); + stringsEvent[EVENT_INTERFACE_MODS_REFRESH] = TR("Refresh\\Refresh the list of currently installed mods"); stringsEvent[EVENT_INTERFACE_SETUPd] = TR("Device\\Driver and resolution settings"); stringsEvent[EVENT_INTERFACE_SETUPg] = TR("Graphics\\Graphics settings"); stringsEvent[EVENT_INTERFACE_SETUPp] = TR("Game\\Game settings"); diff --git a/src/common/restext.h b/src/common/restext.h index 602b3b9c..0ba5d16d 100644 --- a/src/common/restext.h +++ b/src/common/restext.h @@ -72,6 +72,7 @@ enum ResTextType RT_TITLE_READ = 51, RT_TITLE_USER = 52, RT_TITLE_PLUS = 53, + RT_TITLE_MODS = 54, RT_PLAY_CHAP_CHAPTERS = 60, RT_PLAY_CHAP_PLANETS = 61, @@ -103,6 +104,11 @@ enum ResTextType RT_DIALOG_OK = 110, RT_DIALOG_NOUSRLVL_TITLE = 111, RT_DIALOG_NOUSRLVL_TEXT = 112, + RT_DIALOG_OPEN_PATH_FAILED_TITLE = 113, + RT_DIALOG_OPEN_PATH_FAILED_TEXT = 114, + RT_DIALOG_OPEN_WEBSITE_FAILED_TITLE = 115, + RT_DIALOG_OPEN_WEBSITE_FAILED_TEXT = 116, + RT_DIALOG_CHANGES_QUESTION = 117, RT_STUDIO_LISTTT = 120, RT_STUDIO_COMPOK = 121, @@ -148,6 +154,18 @@ enum ResTextType RT_SCOREBOARD_RESULTS_TIME= 232, RT_SCOREBOARD_RESULTS_LINE= 233, + RT_MOD_LIST = 234, + RT_MOD_DETAILS = 235, + RT_MOD_SUMMARY = 236, + RT_MOD_ENABLE = 237, + RT_MOD_DISABLE = 238, + RT_MOD_UNKNOWN_AUTHOR = 239, + RT_MOD_AUTHOR_FIELD_NAME = 240, + RT_MOD_VERSION_FIELD_NAME = 241, + RT_MOD_WEBSITE_FIELD_NAME = 242, + RT_MOD_CHANGES_FIELD_NAME = 243, + RT_MOD_NO_SUMMARY = 244, + RT_MOD_NO_CHANGES = 245, RT_MAX //! < number of values }; diff --git a/src/common/system/system.cpp b/src/common/system/system.cpp index a815964d..b4893655 100644 --- a/src/common/system/system.cpp +++ b/src/common/system/system.cpp @@ -215,3 +215,13 @@ std::string CSystemUtils::GetEnvVar(const std::string& name) { return ""; } + +bool CSystemUtils::OpenPath(const std::string& path) +{ + return false; +} + +bool CSystemUtils::OpenWebsite(const std::string& url) +{ + return false; +} diff --git a/src/common/system/system.h b/src/common/system/system.h index ebc92269..f20ee443 100644 --- a/src/common/system/system.h +++ b/src/common/system/system.h @@ -145,6 +145,14 @@ public: //! Returns the environment variable with the given name or an empty string if it does not exist virtual std::string GetEnvVar(const std::string &name); + //! Opens a path with default file browser + /** \returns true if successful */ + virtual bool OpenPath(const std::string& path); + + //! Opens a website with default web browser + /** \returns true if successful */ + virtual bool OpenWebsite(const std::string& url); + //! Sleep for given amount of microseconds virtual void Usleep(int usecs) = 0; diff --git a/src/common/system/system_linux.cpp b/src/common/system/system_linux.cpp index 459c071f..2b7d99fa 100644 --- a/src/common/system/system_linux.cpp +++ b/src/common/system/system_linux.cpp @@ -150,6 +150,28 @@ std::string CSystemUtilsLinux::GetEnvVar(const std::string& name) return ""; } +bool CSystemUtilsLinux::OpenPath(const std::string& path) +{ + int result = system(("xdg-open \"" + path + "\"").c_str()); + if (result != 0) + { + GetLogger()->Error("Failed to open path: %s, error code: %i\n", path.c_str(), result); + return false; + } + return true; +} + +bool CSystemUtilsLinux::OpenWebsite(const std::string& url) +{ + int result = system(("xdg-open \"" + url + "\"").c_str()); + if (result != 0) + { + GetLogger()->Error("Failed to open website: %s, error code: %i\n", url.c_str(), result); + return false; + } + return true; +} + void CSystemUtilsLinux::Usleep(int usec) { usleep(usec); diff --git a/src/common/system/system_linux.h b/src/common/system/system_linux.h index b3446aa6..6607b150 100644 --- a/src/common/system/system_linux.h +++ b/src/common/system/system_linux.h @@ -48,6 +48,9 @@ public: std::string GetEnvVar(const std::string& name) override; + bool OpenPath(const std::string& path) override; + bool OpenWebsite(const std::string& url) override; + void Usleep(int usec) override; private: diff --git a/src/common/system/system_macosx.cpp b/src/common/system/system_macosx.cpp index 752cf98f..d65fb6dc 100644 --- a/src/common/system/system_macosx.cpp +++ b/src/common/system/system_macosx.cpp @@ -119,6 +119,28 @@ std::string CSystemUtilsMacOSX::GetEnvVar(const std::string& str) return std::string(); } +bool CSystemUtilsMacOSX::OpenPath(const std::string& path) +{ + int result = system(("open \"" + path + "\"").c_str()); // TODO: Test on macOS + if (result != 0) + { + GetLogger()->Error("Failed to open path: %s, error code: %i\n", path.c_str(), result); + return false; + } + return true; +} + +bool CSystemUtilsMacOSX::OpenWebsite(const std::string& url) +{ + int result = system(("open \"" + url + "\"").c_str()); // TODO: Test on macOS + if (result != 0) + { + GetLogger()->Error("Failed to open website: %s, error code: %i\n", website.c_str(), result); + return false; + } + return true; +} + void CSystemUtilsMacOSX::Usleep(int usec) { usleep(usec); diff --git a/src/common/system/system_macosx.h b/src/common/system/system_macosx.h index dc3f785a..523e52e8 100644 --- a/src/common/system/system_macosx.h +++ b/src/common/system/system_macosx.h @@ -38,6 +38,9 @@ public: std::string GetEnvVar(const std::string& name) override; + bool OpenPath(const std::string& path) override; + bool OpenWebsite(const std::string& url) override; + void Usleep(int usec) override; private: diff --git a/src/common/system/system_windows.cpp b/src/common/system/system_windows.cpp index 413b0575..989d359b 100644 --- a/src/common/system/system_windows.cpp +++ b/src/common/system/system_windows.cpp @@ -21,6 +21,7 @@ #include "common/logger.h" +#include #include @@ -152,6 +153,28 @@ std::string CSystemUtilsWindows::GetEnvVar(const std::string& name) } } +bool CSystemUtilsWindows::OpenPath(const std::string& path) +{ + int result = system(("start explorer \"" + boost::filesystem::path(path).make_preferred().string() + "\"").c_str()); + if (result != 0) + { + GetLogger()->Error("Failed to open path: %s, error code: %i\n", path.c_str(), result); + return false; + } + return true; +} + +bool CSystemUtilsWindows::OpenWebsite(const std::string& url) +{ + int result = system(("rundll32 url.dll,FileProtocolHandler \"" + url + "\"").c_str()); + if (result != 0) + { + GetLogger()->Error("Failed to open website: %s, error code: %i\n", url.c_str(), result); + return false; + } + return true; +} + void CSystemUtilsWindows::Usleep(int usec) { LARGE_INTEGER ft; diff --git a/src/common/system/system_windows.h b/src/common/system/system_windows.h index 4bbd704f..90ef6b91 100644 --- a/src/common/system/system_windows.h +++ b/src/common/system/system_windows.h @@ -46,6 +46,9 @@ public: std::string GetEnvVar(const std::string& name) override; + bool OpenPath(const std::string& path) override; + bool OpenWebsite(const std::string& url) override; + void Usleep(int usec) override; public: diff --git a/src/graphics/engine/camera.cpp b/src/graphics/engine/camera.cpp index c55473ab..f3b96186 100644 --- a/src/graphics/engine/camera.cpp +++ b/src/graphics/engine/camera.cpp @@ -59,14 +59,14 @@ static void SetTransparency(CObject* obj, float value) if (obj->Implements(ObjectInterfaceType::Carrier)) { - CObject* cargo = dynamic_cast(obj)->GetCargo(); + CObject* cargo = dynamic_cast(*obj).GetCargo(); if (cargo != nullptr) cargo->SetTransparency(value); } if (obj->Implements(ObjectInterfaceType::Powered)) { - CObject* power = dynamic_cast(obj)->GetPower(); + CObject* power = dynamic_cast(*obj).GetPower(); if (power != nullptr) power->SetTransparency(value); } @@ -1233,7 +1233,7 @@ bool CCamera::EventFrameBack(const Event &event) bool ground = true; if (m_cameraObj->Implements(ObjectInterfaceType::Movable)) - ground = dynamic_cast(m_cameraObj)->GetPhysics()->GetLand(); + ground = dynamic_cast(*m_cameraObj).GetPhysics()->GetLand(); if ( ground ) // ground? { Math::Vector pos = lookatPt + (lookatPt - m_eyePt); @@ -1326,7 +1326,7 @@ bool CCamera::EventFrameOnBoard(const Event &event) { assert(m_cameraObj->Implements(ObjectInterfaceType::Controllable)); Math::Vector lookatPt, upVec; - dynamic_cast(m_cameraObj)->AdjustCamera(m_eyePt, m_directionH, m_directionV, lookatPt, upVec, m_type); + dynamic_cast(*m_cameraObj).AdjustCamera(m_eyePt, m_directionH, m_directionV, lookatPt, upVec, m_type); Math::Vector eye = m_effectOffset * 0.3f + m_eyePt; Math::Vector lookat = m_effectOffset * 0.3f + lookatPt; diff --git a/src/graphics/engine/engine.cpp b/src/graphics/engine/engine.cpp index 198e230d..c146d767 100644 --- a/src/graphics/engine/engine.cpp +++ b/src/graphics/engine/engine.cpp @@ -408,6 +408,7 @@ void CEngine::ReloadAllTextures() { FlushTextureCache(); m_text->FlushCache(); + m_text->ReloadFonts(); m_app->GetEventQueue()->AddEvent(Event(EVENT_RELOAD_TEXTURES)); UpdateGroundSpotTextures(); diff --git a/src/graphics/engine/engine.h b/src/graphics/engine/engine.h index af455819..aee24f34 100644 --- a/src/graphics/engine/engine.h +++ b/src/graphics/engine/engine.h @@ -1189,6 +1189,10 @@ public: void EnablePauseBlur(); void DisablePauseBlur(); + //! Reloads all textures + /** This additionally sends EVENT_RELOAD_TEXTURES to reload all textures not maintained by CEngine **/ + void ReloadAllTextures(); + protected: //! Resets some states and flushes textures after device was changed (e.g. resoulution changed) /** Instead of calling this directly, send EVENT_RESOLUTION_CHANGED event **/ @@ -1287,10 +1291,6 @@ protected: }; static void WriteScreenShotThread(std::unique_ptr data); - //! Reloads all textures - /** This additionally sends EVENT_RELOAD_TEXTURES to reload all textures not maintained by CEngine **/ - void ReloadAllTextures(); - protected: CApplication* m_app; CSystemUtils* m_systemUtils; diff --git a/src/graphics/engine/lightning.cpp b/src/graphics/engine/lightning.cpp index 84a99bbc..8b101e53 100644 --- a/src/graphics/engine/lightning.cpp +++ b/src/graphics/engine/lightning.cpp @@ -323,7 +323,7 @@ CObject* CLightning::SearchObject(Math::Vector pos) if (!obj->Implements(ObjectInterfaceType::Destroyable)) continue; - float detect = m_magnetic * dynamic_cast(obj)->GetLightningHitProbability(); + float detect = m_magnetic * dynamic_cast(*obj).GetLightningHitProbability(); if (detect == 0.0f) continue; Math::Vector oPos = obj->GetPosition(); diff --git a/src/graphics/engine/oldmodelmanager.cpp b/src/graphics/engine/oldmodelmanager.cpp index ee155ce8..7207cc7b 100644 --- a/src/graphics/engine/oldmodelmanager.cpp +++ b/src/graphics/engine/oldmodelmanager.cpp @@ -57,7 +57,18 @@ bool COldModelManager::LoadModel(const std::string& fileName, bool mirrored, int if (!stream.is_open()) throw CModelIOException(std::string("Could not open file '") + fileName + "'"); - model = ModelInput::Read(stream, ModelFormat::Old); + std::string::size_type extension_index = fileName.find_last_of('.'); + if (extension_index == std::string::npos) + throw CModelIOException(std::string("Filename '") + fileName + "' has no extension"); + + std::string extension = fileName.substr(extension_index + 1); + + if (extension == "mod") + model = ModelInput::Read(stream, ModelFormat::Old); + else if (extension == "txt") + model = ModelInput::Read(stream, ModelFormat::Text); + else + throw CModelIOException(std::string("Filename '") + fileName + "' has unknown extension"); } catch (const CModelIOException& e) { diff --git a/src/graphics/engine/particle.cpp b/src/graphics/engine/particle.cpp index 07370493..fa430488 100644 --- a/src/graphics/engine/particle.cpp +++ b/src/graphics/engine/particle.cpp @@ -953,7 +953,7 @@ void CParticle::FrameParticle(float rTime) m_particle[i].goal = m_particle[i].pos; if (object != nullptr && object->Implements(ObjectInterfaceType::Damageable)) { - dynamic_cast(object)->DamageObject(DamageType::Phazer, 0.002f, m_particle[i].objFather); + dynamic_cast(*object).DamageObject(DamageType::Phazer, 0.002f, m_particle[i].objFather); } m_particle[i].zoom = 1.0f-(m_particle[i].time-m_particle[i].duration); @@ -1156,7 +1156,7 @@ void CParticle::FrameParticle(float rTime) { if (object->Implements(ObjectInterfaceType::Damageable)) { - dynamic_cast(object)->DamageObject(DamageType::Fire, 0.001f, m_particle[i].objFather); + dynamic_cast(*object).DamageObject(DamageType::Fire, 0.001f, m_particle[i].objFather); } m_exploGunCounter++; @@ -1222,7 +1222,7 @@ void CParticle::FrameParticle(float rTime) m_particle[i].goal = m_particle[i].pos; if (object != nullptr) { - if (object->GetType() == OBJECT_MOBILErs && dynamic_cast(object)->GetActiveShieldRadius() > 0.0f) // protected by shield? + if (object->GetType() == OBJECT_MOBILErs && dynamic_cast(*object).GetActiveShieldRadius() > 0.0f) // protected by shield? { CreateParticle(m_particle[i].pos, Math::Vector(0.0f, 0.0f, 0.0f), Math::Point(6.0f, 6.0f), PARTIGUNDEL, 2.0f); if (m_lastTimeGunDel > 0.2f) @@ -1240,7 +1240,7 @@ void CParticle::FrameParticle(float rTime) if (object->Implements(ObjectInterfaceType::Damageable)) { - dynamic_cast(object)->DamageObject(DamageType::Organic, 0.1f, m_particle[i].objFather); // starts explosion + dynamic_cast(*object).DamageObject(DamageType::Organic, 0.1f, m_particle[i].objFather); // starts explosion } } } @@ -1270,7 +1270,7 @@ void CParticle::FrameParticle(float rTime) m_particle[i].goal = m_particle[i].pos; if (object != nullptr) { - if (object->GetType() == OBJECT_MOBILErs && dynamic_cast(object)->GetActiveShieldRadius() > 0.0f) + if (object->GetType() == OBJECT_MOBILErs && dynamic_cast(*object).GetActiveShieldRadius() > 0.0f) { CreateParticle(m_particle[i].pos, Math::Vector(0.0f, 0.0f, 0.0f), Math::Point(6.0f, 6.0f), PARTIGUNDEL, 2.0f); if (m_lastTimeGunDel > 0.2f) @@ -1285,7 +1285,7 @@ void CParticle::FrameParticle(float rTime) { if (object->Implements(ObjectInterfaceType::Damageable)) { - dynamic_cast(object)->DamageObject(DamageType::Fire, std::numeric_limits::infinity(), m_particle[i].objFather); // starts explosion + dynamic_cast(*object).DamageObject(DamageType::Fire, std::numeric_limits::infinity(), m_particle[i].objFather); // starts explosion } } } @@ -1344,7 +1344,7 @@ void CParticle::FrameParticle(float rTime) { if (object->Implements(ObjectInterfaceType::Damageable)) { - dynamic_cast(object)->DamageObject(DamageType::Organic, 0.001f, m_particle[i].objFather); + dynamic_cast(*object).DamageObject(DamageType::Organic, 0.001f, m_particle[i].objFather); } m_exploGunCounter ++; @@ -2422,7 +2422,7 @@ void CParticle::FrameParticle(float rTime) if (object != nullptr) { assert(object->Implements(ObjectInterfaceType::Damageable)); - dynamic_cast(object)->DamageObject(DamageType::Tower, std::numeric_limits::infinity(), m_particle[i].objFather); + dynamic_cast(*object).DamageObject(DamageType::Tower, std::numeric_limits::infinity(), m_particle[i].objFather); } } diff --git a/src/graphics/engine/pyro.cpp b/src/graphics/engine/pyro.cpp index d295985a..33c144b8 100644 --- a/src/graphics/engine/pyro.cpp +++ b/src/graphics/engine/pyro.cpp @@ -129,7 +129,7 @@ bool CPyro::Create(PyroType type, CObject* obj, float force) CObject* power = nullptr; if (obj->Implements(ObjectInterfaceType::Powered)) - power = dynamic_cast(obj)->GetPower(); + power = dynamic_cast(*obj).GetPower(); if (power == nullptr) { @@ -260,7 +260,7 @@ bool CPyro::Create(PyroType type, CObject* obj, float force) m_sound->Play(SOUND_DEADw, m_pos); } assert(m_object->Implements(ObjectInterfaceType::Controllable)); - if ( type == PT_SHOTH && dynamic_cast(m_object)->GetSelect() ) + if ( type == PT_SHOTH && dynamic_cast(*m_object).GetSelect() ) { m_sound->Play(SOUND_AIE, m_pos); m_sound->Play(SOUND_AIE, m_engine->GetEyePt()); @@ -278,10 +278,10 @@ bool CPyro::Create(PyroType type, CObject* obj, float force) if ( m_type == PT_DEADG ) { assert(m_object->Implements(ObjectInterfaceType::Destroyable)); - dynamic_cast(m_object)->SetDying(DeathType::Dead); + dynamic_cast(*m_object).SetDying(DeathType::Dead); assert(obj->Implements(ObjectInterfaceType::Movable)); - dynamic_cast(obj)->GetMotion()->SetAction(MHS_DEADg, 1.0f); + dynamic_cast(*obj).GetMotion()->SetAction(MHS_DEADg, 1.0f); m_camera->StartCentering(m_object, Math::PI*0.5f, 99.9f, 0.0f, 1.5f); m_camera->StartOver(CAM_OVER_EFFECT_FADEOUT_WHITE, m_pos, 1.0f); @@ -291,10 +291,10 @@ bool CPyro::Create(PyroType type, CObject* obj, float force) if ( m_type == PT_DEADW ) { assert(m_object->Implements(ObjectInterfaceType::Destroyable)); - dynamic_cast(m_object)->SetDying(DeathType::Dead); + dynamic_cast(*m_object).SetDying(DeathType::Dead); assert(obj->Implements(ObjectInterfaceType::Movable)); - dynamic_cast(obj)->GetMotion()->SetAction(MHS_DEADw, 1.0f); + dynamic_cast(*obj).GetMotion()->SetAction(MHS_DEADw, 1.0f); m_camera->StartCentering(m_object, Math::PI*0.5f, 99.9f, 0.0f, 3.0f); m_camera->StartOver(CAM_OVER_EFFECT_FADEOUT_BLACK, m_pos, 1.0f); @@ -312,7 +312,7 @@ bool CPyro::Create(PyroType type, CObject* obj, float force) if ( m_type == PT_SHOTH ) { assert(m_object->Implements(ObjectInterfaceType::Controllable)); - if ( m_camera->GetBlood() && dynamic_cast(m_object)->GetSelect() ) + if ( m_camera->GetBlood() && dynamic_cast(*m_object).GetSelect() ) { m_camera->StartOver(CAM_OVER_EFFECT_BLOOD, m_pos, force); } @@ -1413,7 +1413,7 @@ void CPyro::DeleteObject(bool primary, bool secondary) if (m_object->Implements(ObjectInterfaceType::Transportable)) { // TODO: this should be handled in the object's destructor - CObject* transporter = dynamic_cast(m_object)->GetTransporter(); + CObject* transporter = dynamic_cast(*m_object).GetTransporter(); if (transporter != nullptr) { if (transporter->Implements(ObjectInterfaceType::Powered)) @@ -1582,12 +1582,12 @@ void CPyro::ExploStart() m_object->Simplify(); m_object->SetLock(true); // ruin not usable yet assert(m_object->Implements(ObjectInterfaceType::Destroyable)); - dynamic_cast(m_object)->SetDying(DeathType::Exploding); // being destroyed + dynamic_cast(*m_object).SetDying(DeathType::Exploding); // being destroyed m_object->FlatParent(); - if ( m_object->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(m_object)->GetSelect() ) + if ( m_object->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(*m_object).GetSelect() ) { - dynamic_cast(m_object)->SetSelect(false); // deselects the object + dynamic_cast(*m_object).SetSelect(false); // deselects the object m_camera->SetType(CAM_TYPE_EXPLO); m_main->DeselectAll(); } @@ -1611,7 +1611,7 @@ void CPyro::ExploStart() // TODO: temporary hack (hopefully) assert(m_object->Implements(ObjectInterfaceType::Old)); - Math::Vector pos = dynamic_cast(m_object)->GetPartPosition(i); + Math::Vector pos = dynamic_cast(*m_object).GetPartPosition(i); Math::Vector speed; float weight; @@ -1658,9 +1658,9 @@ void CPyro::BurnStart() m_object->Simplify(); m_object->SetLock(true); // ruin not usable yet - if ( m_object->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(m_object)->GetSelect() ) + if ( m_object->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(*m_object).GetSelect() ) { - dynamic_cast(m_object)->SetSelect(false); // deselects the object + dynamic_cast(*m_object).SetSelect(false); // deselects the object m_camera->SetType(CAM_TYPE_EXPLO); m_main->DeselectAll(); } @@ -2198,7 +2198,7 @@ void CPyro::BurnProgress() if (m_object->Implements(ObjectInterfaceType::Powered)) { - CObject* sub = dynamic_cast(m_object)->GetPower(); + CObject* sub = dynamic_cast(*m_object).GetPower(); if (sub != nullptr) // is there a battery? sub->SetScaleY(1.0f - m_progress); // complete flattening } @@ -2292,7 +2292,7 @@ CObject* CPyro::FallSearchBeeExplo() if (obj->GetType() == OBJECT_MOBILErs) { - float shieldRadius = dynamic_cast(obj)->GetActiveShieldRadius(); + float shieldRadius = dynamic_cast(*obj).GetActiveShieldRadius(); if ( shieldRadius > 0.0f ) { float distance = Math::Distance(oPos, bulletCrashSphere.sphere.pos); @@ -2360,12 +2360,12 @@ void CPyro::FallProgress(float rTime) { assert(m_object->Implements(ObjectInterfaceType::Destroyable)); // TODO: implement "killer"? - dynamic_cast(m_object)->DestroyObject(DestructionType::Explosion); + dynamic_cast(*m_object).DestroyObject(DestructionType::Explosion); } } else { - if (obj->GetType() == OBJECT_MOBILErs && dynamic_cast(obj)->GetActiveShieldRadius() > 0.0f) // protected by shield? + if (obj->GetType() == OBJECT_MOBILErs && dynamic_cast(*obj).GetActiveShieldRadius() > 0.0f) // protected by shield? { m_particle->CreateParticle(pos, Math::Vector(0.0f, 0.0f, 0.0f), Math::Point(6.0f, 6.0f), PARTIGUNDEL, 2.0f, 0.0f, 0.0f); @@ -2376,7 +2376,7 @@ void CPyro::FallProgress(float rTime) else { assert(obj->Implements(ObjectInterfaceType::Damageable)); - if (dynamic_cast(obj)->DamageObject(DamageType::FallingObject)) + if (dynamic_cast(*obj).DamageObject(DamageType::FallingObject)) { DeleteObject(true, true); // removes the ball } @@ -2384,7 +2384,7 @@ void CPyro::FallProgress(float rTime) { assert(m_object->Implements(ObjectInterfaceType::Destroyable)); // TODO: implement "killer"? - dynamic_cast(m_object)->DestroyObject(DestructionType::Explosion); + dynamic_cast(*m_object).DestroyObject(DestructionType::Explosion); } } } diff --git a/src/graphics/engine/text.cpp b/src/graphics/engine/text.cpp index 484a7374..fef37333 100644 --- a/src/graphics/engine/text.cpp +++ b/src/graphics/engine/text.cpp @@ -191,17 +191,32 @@ CText::~CText() bool CText::Create() { - CFontLoader fontLoader; - if (!fontLoader.Init()) - { - GetLogger()->Warn("Error on parsing fonts config file: failed to open file\n"); - } if (TTF_Init() != 0) { m_error = std::string("TTF_Init error: ") + std::string(TTF_GetError()); return false; } + if (!ReloadFonts()) + { + return false; + } + + return true; +} + +bool CText::ReloadFonts() +{ + CFontLoader fontLoader; + if (!fontLoader.Init()) + { + GetLogger()->Debug("Error on parsing fonts config file: failed to open file\n"); + } + + // Backup previous fonts + auto fonts = std::move(m_fonts); + m_fonts.clear(); + for (auto type : {FONT_COMMON, FONT_STUDIO, FONT_SATCOM}) { m_fonts[static_cast(type)] = MakeUnique(fontLoader.GetFont(type)); @@ -214,7 +229,10 @@ bool CText::Create() FontType type = (*it).first; CachedFont* cf = GetOrOpenFont(type, m_defaultSize); if (cf == nullptr || cf->font == nullptr) + { + m_fonts = std::move(fonts); return false; + } } return true; @@ -430,7 +448,7 @@ float CText::GetStringWidth(const std::string &text, UTF8Char ch; - int len = StrUtils::Utf8CharSizeAt(text, index); + int len = GetCharSizeAt(font, text, index); if (len >= 1) ch.c1 = text[index]; if (len >= 2) @@ -441,7 +459,7 @@ float CText::GetStringWidth(const std::string &text, width += GetCharWidth(ch, font, size, width); index += len; - fmtIndex++; + fmtIndex += len; } return width; @@ -565,7 +583,7 @@ int CText::Justify(const std::string &text, std::vector::iterator UTF8Char ch; - int len = StrUtils::Utf8CharSizeAt(text, index); + int len = GetCharSizeAt(font, text, index); if (len >= 1) ch.c1 = text[index]; if (len >= 2) @@ -589,7 +607,7 @@ int CText::Justify(const std::string &text, std::vector::iterator } index += len; - fmtIndex++; + fmtIndex += len; } return index; @@ -606,7 +624,7 @@ int CText::Justify(const std::string &text, FontType font, float size, float wid { UTF8Char ch; - int len = StrUtils::Utf8CharSizeAt(text, index); + int len = GetCharSizeAt(font, text, index); if (len >= 1) ch.c1 = text[index]; if (len >= 2) @@ -648,12 +666,9 @@ int CText::Detect(const std::string &text, std::vector::iterator f if (format + fmtIndex != end) font = static_cast(*(format + fmtIndex) & FONT_MASK_FONT); - // TODO: if (font == FONT_BUTTON) - //if (font == FONT_BUTTON) continue; - UTF8Char ch; - int len = StrUtils::Utf8CharSizeAt(text, index); + int len = GetCharSizeAt(font, text, index); if (len >= 1) ch.c1 = text[index]; if (len >= 2) @@ -670,7 +685,7 @@ int CText::Detect(const std::string &text, std::vector::iterator f pos += width; index += len; - fmtIndex++; + fmtIndex += len; } return index; @@ -686,7 +701,7 @@ int CText::Detect(const std::string &text, FontType font, float size, float offs { UTF8Char ch; - int len = StrUtils::Utf8CharSizeAt(text, index); + int len = GetCharSizeAt(font, text, index); if (len >= 1) ch.c1 = text[index]; if (len >= 2) @@ -898,16 +913,7 @@ void CText::StringToUTFCharList(const std::string &text, std::vector & if (format + index != end) font = static_cast(*(format + index) & FONT_MASK_FONT); - int len; - - if(font == FONT_BUTTON) - { - len = 1; - } - else - { - len = StrUtils::Utf8CharSizeAt(text, index); - } + int len = GetCharSizeAt(font, text, index); if (len >= 1) ch.c1 = text[index]; @@ -922,6 +928,20 @@ void CText::StringToUTFCharList(const std::string &text, std::vector & } } +int CText::GetCharSizeAt(Gfx::FontType font, const std::string& text, unsigned int index) const +{ + int len = 0; + if (font == FONT_BUTTON) + { + len = 1; + } + else + { + len = StrUtils::Utf8CharSizeAt(text, index); + } + return len; +} + void CText::DrawString(const std::string &text, FontType font, float size, Math::IntPoint pos, int width, int eol, Color color) { diff --git a/src/graphics/engine/text.h b/src/graphics/engine/text.h index 3a5bff88..dc1be3db 100644 --- a/src/graphics/engine/text.h +++ b/src/graphics/engine/text.h @@ -256,6 +256,8 @@ public: //! Flushes cached textures void FlushCache(); + //! Try to load new font files + bool ReloadFonts(); //@{ //! Tab size management @@ -337,6 +339,8 @@ protected: void StringToUTFCharList(const std::string &text, std::vector &chars); void StringToUTFCharList(const std::string &text, std::vector &chars, std::vector::iterator format, std::vector::iterator end); + int GetCharSizeAt(Gfx::FontType font, const std::string& text, unsigned int index) const; + protected: CEngine* m_engine; CDevice* m_device; diff --git a/src/graphics/opengl/gl21device.cpp b/src/graphics/opengl/gl21device.cpp index b77a8f81..e0782271 100644 --- a/src/graphics/opengl/gl21device.cpp +++ b/src/graphics/opengl/gl21device.cpp @@ -388,6 +388,7 @@ bool CGL21Device::Create() uni.modelMatrix = glGetUniformLocation(m_normalProgram, "uni_ModelMatrix"); uni.normalMatrix = glGetUniformLocation(m_normalProgram, "uni_NormalMatrix"); uni.shadowMatrix = glGetUniformLocation(m_normalProgram, "uni_ShadowMatrix"); + uni.cameraPosition = glGetUniformLocation(m_normalProgram, "uni_CameraPosition"); uni.primaryTexture = glGetUniformLocation(m_normalProgram, "uni_PrimaryTexture"); uni.secondaryTexture = glGetUniformLocation(m_normalProgram, "uni_SecondaryTexture"); @@ -408,6 +409,7 @@ bool CGL21Device::Create() uni.fogColor = glGetUniformLocation(m_normalProgram, "uni_FogColor"); uni.shadowColor = glGetUniformLocation(m_normalProgram, "uni_ShadowColor"); + uni.shadowTexelSize = glGetUniformLocation(m_normalProgram, "uni_ShadowTexelSize"); uni.lightCount = glGetUniformLocation(m_normalProgram, "uni_LightCount"); uni.ambientColor = glGetUniformLocation(m_normalProgram, "uni_Material.ambient"); @@ -441,6 +443,7 @@ bool CGL21Device::Create() glUniformMatrix4fv(uni.modelMatrix, 1, GL_FALSE, matrix.Array()); glUniformMatrix4fv(uni.normalMatrix, 1, GL_FALSE, matrix.Array()); glUniformMatrix4fv(uni.shadowMatrix, 1, GL_FALSE, matrix.Array()); + glUniform3f(uni.cameraPosition, 0.0f, 0.0f, 0.0f); glUniform1i(uni.primaryTexture, 0); glUniform1i(uni.secondaryTexture, 1); @@ -457,6 +460,7 @@ bool CGL21Device::Create() glUniform4f(uni.fogColor, 0.8f, 0.8f, 0.8f, 1.0f); glUniform1f(uni.shadowColor, 0.5f); + glUniform1f(uni.shadowTexelSize, 0.5f); glUniform1i(uni.lightCount, 0); } @@ -653,6 +657,7 @@ void CGL21Device::SetTransform(TransformType type, const Math::Matrix &matrix) else if (type == TRANSFORM_VIEW) { Math::Matrix scale; + Math::Vector cameraPosition; scale.Set(3, 3, -1.0f); m_viewMat = Math::MultiplyMatrices(scale, matrix); @@ -660,6 +665,13 @@ void CGL21Device::SetTransform(TransformType type, const Math::Matrix &matrix) m_combinedMatrix = Math::MultiplyMatrices(m_projectionMat, m_modelviewMat); glUniformMatrix4fv(m_uniforms[m_mode].viewMatrix, 1, GL_FALSE, m_viewMat.Array()); + + if (m_uniforms[m_mode].cameraPosition >= 0) + { + cameraPosition.LoadZero(); + cameraPosition = MatrixVectorMultiply(m_viewMat.Inverse(), cameraPosition); + glUniform3fv(m_uniforms[m_mode].cameraPosition, 1, cameraPosition.Array()); + } } else if (type == TRANSFORM_PROJECTION) { @@ -1439,6 +1451,7 @@ void CGL21Device::SetRenderState(RenderState state, bool enabled) } else if (state == RENDER_STATE_SHADOW_MAPPING) { + glUniform1f(m_uniforms[m_mode].shadowTexelSize, 1.0/m_currentTextures[TEXTURE_SHADOW].size.x); SetTextureEnabled(TEXTURE_SHADOW, enabled); return; diff --git a/src/graphics/opengl/gl33device.cpp b/src/graphics/opengl/gl33device.cpp index cfcb28f2..bd26d35b 100644 --- a/src/graphics/opengl/gl33device.cpp +++ b/src/graphics/opengl/gl33device.cpp @@ -363,6 +363,7 @@ bool CGL33Device::Create() uni.modelMatrix = glGetUniformLocation(m_normalProgram, "uni_ModelMatrix"); uni.normalMatrix = glGetUniformLocation(m_normalProgram, "uni_NormalMatrix"); uni.shadowMatrix = glGetUniformLocation(m_normalProgram, "uni_ShadowMatrix"); + uni.cameraPosition = glGetUniformLocation(m_normalProgram, "uni_CameraPosition"); uni.primaryTexture = glGetUniformLocation(m_normalProgram, "uni_PrimaryTexture"); uni.secondaryTexture = glGetUniformLocation(m_normalProgram, "uni_SecondaryTexture"); @@ -420,6 +421,7 @@ bool CGL33Device::Create() glUniformMatrix4fv(uni.modelMatrix, 1, GL_FALSE, matrix.Array()); glUniformMatrix4fv(uni.normalMatrix, 1, GL_FALSE, matrix.Array()); glUniformMatrix4fv(uni.shadowMatrix, 1, GL_FALSE, matrix.Array()); + glUniform3f(uni.cameraPosition, 0.0f, 0.0f, 0.0f); glUniform1i(uni.primaryTexture, 0); glUniform1i(uni.secondaryTexture, 1); @@ -660,6 +662,7 @@ void CGL33Device::SetTransform(TransformType type, const Math::Matrix &matrix) else if (type == TRANSFORM_VIEW) { Math::Matrix scale; + Math::Vector cameraPosition; scale.Set(3, 3, -1.0f); m_viewMat = Math::MultiplyMatrices(scale, matrix); @@ -667,6 +670,13 @@ void CGL33Device::SetTransform(TransformType type, const Math::Matrix &matrix) m_combinedMatrixOutdated = true; glUniformMatrix4fv(m_uni->viewMatrix, 1, GL_FALSE, m_viewMat.Array()); + + if (m_uni->cameraPosition >= 0) + { + cameraPosition.LoadZero(); + cameraPosition = MatrixVectorMultiply(m_viewMat.Inverse(), cameraPosition); + glUniform3fv(m_uni->cameraPosition, 1, cameraPosition.Array()); + } } else if (type == TRANSFORM_PROJECTION) { @@ -876,6 +886,8 @@ void CGL33Device::UpdateTexture(const Texture& texture, Math::IntPoint offset, I glTexSubImage2D(GL_TEXTURE_2D, 0, offset.x, offset.y, texData.actualSurface->w, texData.actualSurface->h, texData.sourceFormat, GL_UNSIGNED_BYTE, texData.actualSurface->pixels); + glGenerateMipmap(GL_TEXTURE_2D); + SDL_FreeSurface(texData.convertedSurface); } diff --git a/src/graphics/opengl/glutil.h b/src/graphics/opengl/glutil.h index 8c67c378..f92888a4 100644 --- a/src/graphics/opengl/glutil.h +++ b/src/graphics/opengl/glutil.h @@ -167,6 +167,8 @@ struct UniformLocations GLint shadowMatrix = -1; //! Normal matrix GLint normalMatrix = -1; + //! Camera position + GLint cameraPosition = -1; //! Primary texture sampler GLint primaryTexture = -1; @@ -193,6 +195,8 @@ struct UniformLocations //! Shadow color GLint shadowColor = -1; + //! Shadow texel size + GLint shadowTexelSize = -1; // Number of enabled lights GLint lightCount = -1; diff --git a/src/graphics/opengl/shaders/gl21/fs_normal.glsl b/src/graphics/opengl/shaders/gl21/fs_normal.glsl index d21bc9cf..1aeffcbe 100644 --- a/src/graphics/opengl/shaders/gl21/fs_normal.glsl +++ b/src/graphics/opengl/shaders/gl21/fs_normal.glsl @@ -35,6 +35,7 @@ uniform vec2 uni_FogRange; uniform vec4 uni_FogColor; uniform float uni_ShadowColor; +uniform float uni_ShadowTexelSize; struct LightParams { @@ -56,6 +57,7 @@ uniform Material uni_Material; uniform int uni_LightCount; uniform LightParams uni_Light[4]; +varying vec3 pass_CameraDirection; varying float pass_Distance; varying vec4 pass_Color; varying vec3 pass_Normal; @@ -76,16 +78,17 @@ void main() vec4 specular = vec4(0.0f); vec3 normal = normalize(pass_Normal); + vec3 camera = normalize(pass_CameraDirection); for (int i = 0; i < uni_LightCount; i++) { LightParams light = uni_Light[i]; vec3 lightDirection = light.Position.xyz; - vec3 reflectDirection = -reflect(lightDirection, normal); + vec3 reflectAxis = normalize(normalize(lightDirection) + camera); float diffuseComponent = clamp(dot(normal, lightDirection), 0.0f, 1.0f); - float specularComponent = clamp(pow(dot(normal, lightDirection + reflectDirection), 10.0f), 0.0f, 1.0f); + float specularComponent = pow(clamp(dot(normal, reflectAxis), 0.0f, 1.0f), 10.0f); ambient += light.Ambient; diffuse += diffuseComponent * light.Diffuse; @@ -97,13 +100,11 @@ void main() if (uni_TextureEnabled[2]) { #ifdef CONFIG_QUALITY_SHADOWS - float offset = 0.00025f; - float value = (1.0f / 5.0f) * (shadow2D(uni_ShadowTexture, pass_TexCoord2).x - + shadow2D(uni_ShadowTexture, pass_TexCoord2 + vec3( offset, 0.0f, 0.0f)).x - + shadow2D(uni_ShadowTexture, pass_TexCoord2 + vec3(-offset, 0.0f, 0.0f)).x - + shadow2D(uni_ShadowTexture, pass_TexCoord2 + vec3( 0.0f, offset, 0.0f)).x - + shadow2D(uni_ShadowTexture, pass_TexCoord2 + vec3( 0.0f, -offset, 0.0f)).x); + + shadow2D(uni_ShadowTexture, pass_TexCoord2 + vec3( uni_ShadowTexelSize, 0.0f, 0.0f)).x + + shadow2D(uni_ShadowTexture, pass_TexCoord2 + vec3(-uni_ShadowTexelSize, 0.0f, 0.0f)).x + + shadow2D(uni_ShadowTexture, pass_TexCoord2 + vec3( 0.0f, uni_ShadowTexelSize, 0.0f)).x + + shadow2D(uni_ShadowTexture, pass_TexCoord2 + vec3( 0.0f, -uni_ShadowTexelSize, 0.0f)).x); shadow = mix(uni_ShadowColor, 1.0f, value); #else diff --git a/src/graphics/opengl/shaders/gl21/vs_normal.glsl b/src/graphics/opengl/shaders/gl21/vs_normal.glsl index 9195cd20..41141c4b 100644 --- a/src/graphics/opengl/shaders/gl21/vs_normal.glsl +++ b/src/graphics/opengl/shaders/gl21/vs_normal.glsl @@ -24,7 +24,9 @@ uniform mat4 uni_ViewMatrix; uniform mat4 uni_ModelMatrix; uniform mat4 uni_ShadowMatrix; uniform mat4 uni_NormalMatrix; +uniform vec3 uni_CameraPosition; +varying vec3 pass_CameraDirection; varying float pass_Distance; varying vec4 pass_Color; varying vec3 pass_Normal; @@ -40,6 +42,7 @@ void main() gl_Position = uni_ProjectionMatrix * eyeSpace; + pass_CameraDirection = uni_CameraPosition - position.xyz; pass_Color = gl_Color; pass_Normal = normalize((uni_NormalMatrix * vec4(gl_Normal, 0.0f)).xyz); pass_Distance = abs(eyeSpace.z / eyeSpace.w); diff --git a/src/graphics/opengl/shaders/gl33/fs_normal.glsl b/src/graphics/opengl/shaders/gl33/fs_normal.glsl index e6fd1e07..be58f4fd 100644 --- a/src/graphics/opengl/shaders/gl33/fs_normal.glsl +++ b/src/graphics/opengl/shaders/gl33/fs_normal.glsl @@ -63,6 +63,7 @@ in VertexData vec4 ShadowCoord; vec4 LightColor; float Distance; + vec3 CameraDirection; } data; out vec4 out_FragColor; @@ -78,17 +79,17 @@ void main() vec4 specular = vec4(0.0f); vec3 normal = normalize(data.Normal); + vec3 camera = normalize(data.CameraDirection); for (int i = 0; i < uni_LightCount; i++) { vec3 lightDirection = uni_Light[i].Position.xyz; - - vec3 reflectDirection = -reflect(lightDirection, normal); + vec3 reflectAxis = normalize(normalize(lightDirection) + camera); ambient += uni_Light[i].Ambient; diffuse += clamp(dot(normal, lightDirection), 0.0f, 1.0f) * uni_Light[i].Diffuse; - specular += clamp(pow(dot(normal, lightDirection + reflectDirection), 10.0f), 0.0f, 1.0f) + specular += pow(clamp(dot(normal, reflectAxis), 0.0f, 1.0f), 10.0f) * uni_Light[i].Specular; } diff --git a/src/graphics/opengl/shaders/gl33/vs_normal.glsl b/src/graphics/opengl/shaders/gl33/vs_normal.glsl index aeed3c60..216682f7 100644 --- a/src/graphics/opengl/shaders/gl33/vs_normal.glsl +++ b/src/graphics/opengl/shaders/gl33/vs_normal.glsl @@ -25,6 +25,7 @@ uniform mat4 uni_ViewMatrix; uniform mat4 uni_ModelMatrix; uniform mat4 uni_ShadowMatrix; uniform mat4 uni_NormalMatrix; +uniform vec3 uni_CameraPosition; layout(location = 0) in vec4 in_VertexCoord; layout(location = 1) in vec3 in_Normal; @@ -41,6 +42,7 @@ out VertexData vec4 ShadowCoord; vec4 LightColor; float Distance; + vec3 CameraDirection; } data; void main() @@ -56,4 +58,5 @@ void main() data.Normal = normalize((uni_NormalMatrix * vec4(in_Normal, 0.0f)).xyz); data.ShadowCoord = vec4(shadowCoord.xyz / shadowCoord.w, 1.0f); data.Distance = abs(eyeSpace.z); + data.CameraDirection = uni_CameraPosition - position.xyz; } diff --git a/src/level/mainmovie.cpp b/src/level/mainmovie.cpp index 045a64ed..6a34510d 100644 --- a/src/level/mainmovie.cpp +++ b/src/level/mainmovie.cpp @@ -90,7 +90,7 @@ bool CMainMovie::Start(MainMovieType type, float time) } assert(pObj->Implements(ObjectInterfaceType::Movable)); - dynamic_cast(pObj)->GetMotion()->SetAction(MHS_SATCOM, 0.5f); // reads the SatCom + dynamic_cast(*pObj).GetMotion()->SetAction(MHS_SATCOM, 0.5f); // reads the SatCom m_camera->GetCamera(m_initialEye, m_initialLookat); m_camera->SetType(Gfx::CAM_TYPE_SCRIPT); @@ -110,7 +110,7 @@ bool CMainMovie::Start(MainMovieType type, float time) if ( pObj != nullptr ) { assert(pObj->Implements(ObjectInterfaceType::Movable)); - dynamic_cast(pObj)->GetMotion()->SetAction(-1); // finishes reading SatCom + dynamic_cast(*pObj).GetMotion()->SetAction(-1); // finishes reading SatCom } m_camera->SetType(Gfx::CAM_TYPE_BACK); @@ -132,7 +132,7 @@ bool CMainMovie::Stop() if ( pObj != nullptr ) { assert(pObj->Implements(ObjectInterfaceType::Movable)); - dynamic_cast(pObj)->GetMotion()->SetAction(-1); // finishes reading SatCom + dynamic_cast(*pObj).GetMotion()->SetAction(-1); // finishes reading SatCom } } diff --git a/src/level/parser/parser.cpp b/src/level/parser/parser.cpp index e8f871bd..1bf78a24 100644 --- a/src/level/parser/parser.cpp +++ b/src/level/parser/parser.cpp @@ -41,6 +41,7 @@ #include #include #include +#include CLevelParser::CLevelParser() { @@ -172,13 +173,28 @@ void CLevelParser::Load() boost::replace_all(line, "\t", " "); // replace tab by space // ignore comments - std::size_t comment = line.find("//"); - if (comment != std::string::npos) - line = line.substr(0, comment); + size_t pos = 0; + std::string linesuffix = line; + boost::regex commentRegex{ R"(("[^"]*")|('[^']*')|(//.*$))" }; + boost::smatch matches; + while (boost::regex_search(linesuffix, matches, commentRegex)) + { + if (matches[3].matched) + { + pos += std::distance(linesuffix.cbegin(), matches.prefix().second); + line = line.substr(0, pos); + linesuffix = ""; + } + else + { + pos += std::distance(linesuffix.cbegin(), matches.suffix().first); + linesuffix = matches.suffix().str(); + } + } boost::algorithm::trim(line); - std::size_t pos = line.find_first_of(" \t\n"); + pos = line.find_first_of(" \t\n"); std::string command = line.substr(0, pos); if (pos != std::string::npos) { diff --git a/src/level/robotmain.cpp b/src/level/robotmain.cpp index 25262737..af04448c 100644 --- a/src/level/robotmain.cpp +++ b/src/level/robotmain.cpp @@ -320,6 +320,7 @@ std::string PhaseToString(Phase phase) if (phase == PHASE_APPERANCE) return "PHASE_APPERANCE"; if (phase == PHASE_MAIN_MENU) return "PHASE_MAIN_MENU"; if (phase == PHASE_LEVEL_LIST) return "PHASE_LEVEL_LIST"; + if (phase == PHASE_MOD_LIST) return "PHASE_MOD_LIST"; if (phase == PHASE_SIMUL) return "PHASE_SIMUL"; if (phase == PHASE_SETUPd) return "PHASE_SETUPd"; if (phase == PHASE_SETUPg) return "PHASE_SETUPg"; @@ -1360,7 +1361,7 @@ void CRobotMain::ExecuteCmd(const std::string& cmd) { CObject* object = GetSelect(); if (object != nullptr && object->Implements(ObjectInterfaceType::Shielded)) - dynamic_cast(object)->SetMagnifyDamage(dynamic_cast(object)->GetMagnifyDamage()*0.1f); + dynamic_cast(*object).SetMagnifyDamage(dynamic_cast(*object).GetMagnifyDamage()*0.1f); return; } @@ -1368,7 +1369,7 @@ void CRobotMain::ExecuteCmd(const std::string& cmd) { CObject* object = GetSelect(); if (object != nullptr && object->Implements(ObjectInterfaceType::JetFlying)) - dynamic_cast(object)->SetRange(dynamic_cast(object)->GetRange()*10.0f); + dynamic_cast(*object).SetRange(dynamic_cast(*object).GetRange()*10.0f); return; } @@ -1393,16 +1394,16 @@ void CRobotMain::ExecuteCmd(const std::string& cmd) { if (object->Implements(ObjectInterfaceType::Powered)) { - CObject* power = dynamic_cast(object)->GetPower(); + CObject* power = dynamic_cast(*object).GetPower(); if (power != nullptr && power->Implements(ObjectInterfaceType::PowerContainer)) - dynamic_cast(power)->SetEnergyLevel(1.0f); + dynamic_cast(*power).SetEnergyLevel(1.0f); } if (object->Implements(ObjectInterfaceType::Shielded)) - dynamic_cast(object)->SetShield(1.0f); + dynamic_cast(*object).SetShield(1.0f); if (object->Implements(ObjectInterfaceType::JetFlying)) - dynamic_cast(object)->SetReactorRange(1.0f); + dynamic_cast(*object).SetReactorRange(1.0f); } return; } @@ -1415,9 +1416,9 @@ void CRobotMain::ExecuteCmd(const std::string& cmd) { if (object->Implements(ObjectInterfaceType::Powered)) { - CObject* power = dynamic_cast(object)->GetPower(); + CObject* power = dynamic_cast(*object).GetPower(); if (power != nullptr && power->Implements(ObjectInterfaceType::PowerContainer)) - dynamic_cast(power)->SetEnergyLevel(1.0f); + dynamic_cast(*power).SetEnergyLevel(1.0f); } } return; @@ -1427,7 +1428,7 @@ void CRobotMain::ExecuteCmd(const std::string& cmd) { CObject* object = GetSelect(); if (object != nullptr && object->Implements(ObjectInterfaceType::Shielded)) - dynamic_cast(object)->SetShield(1.0f); + dynamic_cast(*object).SetShield(1.0f); return; } @@ -1437,7 +1438,7 @@ void CRobotMain::ExecuteCmd(const std::string& cmd) if (object != nullptr) { if (object->Implements(ObjectInterfaceType::JetFlying)) - dynamic_cast(object)->SetReactorRange(1.0f); + dynamic_cast(*object).SetReactorRange(1.0f); } return; } @@ -1537,7 +1538,7 @@ void CRobotMain::StartDisplayInfo(int index, bool movie) if (!m_editLock && movie && !m_movie->IsExist() && human) { assert(obj->Implements(ObjectInterfaceType::Movable)); - if (dynamic_cast(obj)->GetMotion()->GetAction() == -1) + if (dynamic_cast(*obj).GetMotion()->GetAction() == -1) { m_movieInfoIndex = index; m_movie->Start(MM_SATCOMopen, 2.5f); @@ -1851,7 +1852,7 @@ CObject* CRobotMain::DeselectAll() void CRobotMain::SelectOneObject(CObject* obj, bool displayError) { assert(obj->Implements(ObjectInterfaceType::Controllable)); - dynamic_cast(obj)->SetSelect(true, displayError); + dynamic_cast(*obj).SetSelect(true, displayError); m_camera->SetControllingObject(obj); ObjectType type = obj->GetType(); @@ -1890,7 +1891,7 @@ void CRobotMain::SelectOneObject(CObject* obj, bool displayError) type == OBJECT_MOBILEdr || type == OBJECT_APOLLO2 ) { - m_camera->SetType(dynamic_cast(obj)->GetCameraType()); + m_camera->SetType(dynamic_cast(*obj).GetCameraType()); } else { @@ -1906,7 +1907,7 @@ bool CRobotMain::SelectObject(CObject* obj, bool displayError) if (m_movieLock || m_editLock) return false; if (m_movie->IsExist()) return false; if (obj != nullptr && - (!obj->Implements(ObjectInterfaceType::Controllable) || !(dynamic_cast(obj)->GetSelectable() || m_cheatSelectInsect))) return false; + (!obj->Implements(ObjectInterfaceType::Controllable) || !(dynamic_cast(*obj).GetSelectable() || m_cheatSelectInsect))) return false; if (m_missionType == MISSION_CODE_BATTLE && m_codeBattleStarted && m_codeBattleSpectator) { @@ -1982,7 +1983,7 @@ CObject* CRobotMain::GetSelect() for (CObject* obj : m_objMan->GetAllObjects()) { if (!obj->Implements(ObjectInterfaceType::Controllable)) continue; - if (dynamic_cast(obj)->GetSelect()) + if (dynamic_cast(*obj).GetSelect()) return obj; } return nullptr; @@ -2000,7 +2001,7 @@ CObject* CRobotMain::DetectObject(Math::Point pos) CObject* transporter = nullptr; if (obj->Implements(ObjectInterfaceType::Transportable)) - transporter = dynamic_cast(obj)->GetTransporter(); + transporter = dynamic_cast(*obj).GetTransporter(); if (transporter != nullptr && !transporter->GetDetectable()) continue; if (obj->GetProxyActivate()) continue; @@ -2008,14 +2009,14 @@ CObject* CRobotMain::DetectObject(Math::Point pos) CObject* target = obj; if (obj->Implements(ObjectInterfaceType::PowerContainer) && obj->Implements(ObjectInterfaceType::Transportable)) { - target = dynamic_cast(obj)->GetTransporter(); // battery connected + target = dynamic_cast(*obj).GetTransporter(); // battery connected if (target == nullptr) { target = obj; // standalone battery } else { - if (!target->Implements(ObjectInterfaceType::Powered) || dynamic_cast(target)->GetPower() != obj) + if (!target->Implements(ObjectInterfaceType::Powered) || dynamic_cast(*target).GetPower() != obj) { // transported, but not in the power slot target = obj; @@ -2045,7 +2046,7 @@ bool CRobotMain::DestroySelectedObject() m_engine->GetPyroManager()->Create(Gfx::PT_FRAGT, obj); - dynamic_cast(obj)->SetSelect(false); // deselects the object + dynamic_cast(*obj).SetSelect(false); // deselects the object m_camera->SetType(Gfx::CAM_TYPE_EXPLO); DeselectAll(); RemoveFromSelectionHistory(obj); @@ -2068,7 +2069,7 @@ void CRobotMain::HiliteClear() for (CObject* obj : m_objMan->GetAllObjects()) { if (!obj->Implements(ObjectInterfaceType::Controllable)) continue; - dynamic_cast(obj)->SetHighlight(false); + dynamic_cast(*obj).SetHighlight(false); } m_map->SetHighlight(nullptr); m_short->SetHighlight(nullptr); @@ -2128,12 +2129,12 @@ void CRobotMain::HiliteObject(Math::Point pos) } } - if (obj->Implements(ObjectInterfaceType::Controllable) && (dynamic_cast(obj)->GetSelectable() || m_cheatSelectInsect)) + if (obj->Implements(ObjectInterfaceType::Controllable) && (dynamic_cast(*obj).GetSelectable() || m_cheatSelectInsect)) { - if (dynamic_cast(obj)->GetSelectable()) + if (dynamic_cast(*obj).GetSelectable()) { // Don't highlight objects that would not be selectable without selectinsect - dynamic_cast(obj)->SetHighlight(true); + dynamic_cast(*obj).SetHighlight(true); } m_map->SetHighlight(obj); m_short->SetHighlight(obj); @@ -2390,7 +2391,7 @@ bool CRobotMain::EventFrame(const Event &event) if (obj->GetType() == OBJECT_TOTO) toto = obj; else if (obj->Implements(ObjectInterfaceType::Interactive)) - dynamic_cast(obj)->EventProcess(event); + dynamic_cast(*obj).EventProcess(event); if ( obj->GetProxyActivate() ) // active if it is near? { @@ -2413,7 +2414,7 @@ bool CRobotMain::EventFrame(const Event &event) continue; if (obj->Implements(ObjectInterfaceType::Interactive)) - dynamic_cast(obj)->EventProcess(event); + dynamic_cast(*obj).EventProcess(event); } m_engine->GetPyroManager()->EventProcess(event); @@ -2437,7 +2438,7 @@ bool CRobotMain::EventFrame(const Event &event) // Advances toto following the camera, because its position depends on the camera. if (toto != nullptr) - dynamic_cast(toto)->EventProcess(event); + dynamic_cast(*toto).EventProcess(event); // NOTE: m_movieLock is set only after the first update of CAutoBase finishes @@ -2671,7 +2672,7 @@ bool CRobotMain::EventObject(const Event &event) { if (obj->Implements(ObjectInterfaceType::Interactive)) { - dynamic_cast(obj)->EventProcess(event); + dynamic_cast(*obj).EventProcess(event); } } @@ -2712,7 +2713,7 @@ void CRobotMain::ScenePerso() obj->SetDrawFront(true); // draws the interface assert(obj->Implements(ObjectInterfaceType::Movable)); - CMotionHuman* mh = static_cast(dynamic_cast(obj)->GetMotion()); + CMotionHuman* mh = static_cast(dynamic_cast(*obj).GetMotion()); mh->StartDisplayPerso(); } } @@ -3150,6 +3151,8 @@ void CRobotMain::CreateScene(bool soluce, bool fixScene, bool resetObject) m_missionType = line->GetParam("type")->AsMissionType(MISSION_NORMAL); m_globalMagnifyDamage = line->GetParam("magnifyDamage")->AsFloat(1.0f); + m_globalNuclearCapacity = line->GetParam("nuclearCapacity")->AsFloat(10.0f); + m_globalCellCapacity = line->GetParam("cellCapacity")->AsFloat(1.0f); continue; } @@ -3380,7 +3383,7 @@ void CRobotMain::CreateScene(bool soluce, bool fixScene, bool resetObject) assert(m_controller->Implements(ObjectInterfaceType::ProgramStorage)); assert(m_controller->Implements(ObjectInterfaceType::Old)); - dynamic_cast(m_controller)->SetCheckToken(false); + dynamic_cast(*m_controller).SetCheckToken(false); if (line->GetParam("script")->IsDefined()) { @@ -3388,7 +3391,7 @@ void CRobotMain::CreateScene(bool soluce, bool fixScene, bool resetObject) Program* program = programStorage->AddProgram(); programStorage->ReadProgram(program, line->GetParam("script")->AsPath("ai")); program->readOnly = true; - dynamic_cast(m_controller)->RunProgram(program); + dynamic_cast(*m_controller).RunProgram(program); } continue; } @@ -3413,7 +3416,7 @@ void CRobotMain::CreateScene(bool soluce, bool fixScene, bool resetObject) if (m_fixScene && obj->GetType() == OBJECT_HUMAN) { assert(obj->Implements(ObjectInterfaceType::Movable)); - CMotion* motion = dynamic_cast(obj)->GetMotion(); + CMotion* motion = dynamic_cast(*obj).GetMotion(); if (m_phase == PHASE_WIN ) motion->SetAction(MHS_WIN, 0.4f); if (m_phase == PHASE_LOST) motion->SetAction(MHS_LOST, 0.5f); } @@ -3428,7 +3431,7 @@ void CRobotMain::CreateScene(bool soluce, bool fixScene, bool resetObject) { CProgramStorageObject* programStorage = dynamic_cast(obj); - if (obj->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(obj)->GetSelectable() && obj->GetType() != OBJECT_HUMAN) + if (obj->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(*obj).GetSelectable() && obj->GetType() != OBJECT_HUMAN) { programStorage->SetProgramStorageIndex(rankObj); } @@ -3817,7 +3820,7 @@ void CRobotMain::CreateScene(bool soluce, bool fixScene, bool resetObject) assert(obj->Implements(ObjectInterfaceType::Controllable)); SelectObject(obj); m_camera->SetControllingObject(obj); - m_camera->SetType(dynamic_cast(obj)->GetCameraType()); + m_camera->SetType(dynamic_cast(*obj).GetCameraType()); } } @@ -3918,6 +3921,7 @@ void CRobotMain::ChangeColor() m_phase != PHASE_SETUPps && m_phase != PHASE_SETUPcs && m_phase != PHASE_SETUPss && + m_phase != PHASE_MOD_LIST && m_phase != PHASE_WIN && m_phase != PHASE_LOST && m_phase != PHASE_APPERANCE ) return; @@ -4413,7 +4417,7 @@ void CRobotMain::StartShowLimit() CObject* obj = GetSelect(); if (obj == nullptr) return; if (!obj->Implements(ObjectInterfaceType::Ranged)) return; - float range = dynamic_cast(obj)->GetShowLimitRadius(); + float range = dynamic_cast(*obj).GetShowLimitRadius(); if (range == 0.0f) return; SetShowLimit(0, Gfx::PARTILIMIT1, obj, obj->GetPosition(), range); } @@ -4583,8 +4587,8 @@ bool CRobotMain::IOIsBusy() { if (! obj->Implements(ObjectInterfaceType::TaskExecutor)) continue; - if (obj->Implements(ObjectInterfaceType::Programmable) && dynamic_cast(obj)->IsProgram()) continue; // TODO: I'm not sure if this is correct but this is how it worked earlier - if (dynamic_cast(obj)->IsForegroundTask()) return true; + if (obj->Implements(ObjectInterfaceType::Programmable) && dynamic_cast(*obj).IsProgram()) continue; // TODO: I'm not sure if this is correct but this is how it worked earlier + if (dynamic_cast(*obj).IsForegroundTask()) return true; } return false; } @@ -4635,7 +4639,7 @@ void CRobotMain::IOWriteObject(CLevelParserLine* line, CObject* obj, const std:: if (obj->Implements(ObjectInterfaceType::Programmable)) { - int run = dynamic_cast(obj)->GetProgramIndex(dynamic_cast(obj)->GetCurrentProgram()); + int run = dynamic_cast(*obj).GetProgramIndex(dynamic_cast(*obj).GetCurrentProgram()); if (run != -1) { line->AddParam("run", MakeUnique(run+1)); @@ -4710,11 +4714,11 @@ bool CRobotMain::IOWriteScene(std::string filename, std::string filecbot, std::s { if (obj->GetType() == OBJECT_TOTO) continue; if (IsObjectBeingTransported(obj)) continue; - if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(obj)->IsDying()) continue; + if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(*obj).IsDying()) continue; if (obj->Implements(ObjectInterfaceType::Carrier)) { - CObject* cargo = dynamic_cast(obj)->GetCargo(); + CObject* cargo = dynamic_cast(*obj).GetCargo(); if (cargo != nullptr) // object transported? { line = MakeUnique("CreateFret"); @@ -4725,7 +4729,7 @@ bool CRobotMain::IOWriteScene(std::string filename, std::string filecbot, std::s if (obj->Implements(ObjectInterfaceType::Powered)) { - CObject* power = dynamic_cast(obj)->GetPower(); + CObject* power = dynamic_cast(*obj).GetPower(); if (power != nullptr) // battery transported? { line = MakeUnique("CreatePower"); @@ -4764,7 +4768,7 @@ bool CRobotMain::IOWriteScene(std::string filename, std::string filecbot, std::s { if (obj->GetType() == OBJECT_TOTO) continue; if (IsObjectBeingTransported(obj)) continue; - if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(obj)->IsDying()) continue; + if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(*obj).IsDying()) continue; if (!SaveFileStack(obj, ostr)) { @@ -4915,7 +4919,7 @@ CObject* CRobotMain::IOReadScene(std::string filename, std::string filecbot) { assert(obj->Implements(ObjectInterfaceType::Carrier)); // TODO: exception? assert(obj->Implements(ObjectInterfaceType::Old)); - dynamic_cast(obj)->SetCargo(cargo); + dynamic_cast(*obj).SetCargo(cargo); auto task = MakeUnique(dynamic_cast(obj)); task->Start(TMO_AUTO, TMA_GRAB); // holds the object! } @@ -4923,9 +4927,9 @@ CObject* CRobotMain::IOReadScene(std::string filename, std::string filecbot) if (power != nullptr) { assert(obj->Implements(ObjectInterfaceType::Powered)); - dynamic_cast(obj)->SetPower(power); + dynamic_cast(*obj).SetPower(power); assert(power->Implements(ObjectInterfaceType::Transportable)); - dynamic_cast(power)->SetTransporter(obj); + dynamic_cast(*power).SetTransporter(obj); } cargo = nullptr; power = nullptr; @@ -4957,7 +4961,7 @@ CObject* CRobotMain::IOReadScene(std::string filename, std::string filecbot) { if (obj->GetType() == OBJECT_TOTO) continue; if (IsObjectBeingTransported(obj)) continue; - if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(obj)->IsDying()) continue; + if (obj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(*obj).IsDying()) continue; if (!ReadFileStack(obj, istr)) { @@ -5314,7 +5318,7 @@ Error CRobotMain::CheckEndMission(bool frame) if (m_base != nullptr && !m_endTakeImmediat) { assert(m_base->Implements(ObjectInterfaceType::Controllable)); - if(dynamic_cast(m_base)->GetSelectable()) + if(dynamic_cast(*m_base).GetSelectable()) return ERR_MISSION_NOTERM; } } @@ -5996,6 +6000,16 @@ float CRobotMain::GetGlobalMagnifyDamage() return m_globalMagnifyDamage; } +float CRobotMain::GetGlobalNuclearCapacity() +{ + return m_globalNuclearCapacity; +} + +float CRobotMain::GetGlobalCellCapacity() +{ + return m_globalCellCapacity; +} + // Beginning of the effect when the instruction "detect" is used. void CRobotMain::StartDetectEffect(COldObject* object, CObject* target) diff --git a/src/level/robotmain.h b/src/level/robotmain.h index df4cbc8f..42590b50 100644 --- a/src/level/robotmain.h +++ b/src/level/robotmain.h @@ -55,6 +55,7 @@ enum Phase PHASE_APPERANCE, PHASE_MAIN_MENU, PHASE_LEVEL_LIST, + PHASE_MOD_LIST, PHASE_SIMUL, PHASE_SETUPd, PHASE_SETUPg, @@ -469,6 +470,11 @@ public: //! Returns global magnifyDamage setting float GetGlobalMagnifyDamage(); + //! Returns global NuclearCell capacity Setting + float GetGlobalNuclearCapacity(); + //! Returns global PowerCell capacity setting + float GetGlobalCellCapacity(); + void StartDetectEffect(COldObject* object, CObject* target); //! Enable crash sphere debug rendering @@ -651,6 +657,9 @@ protected: float m_globalMagnifyDamage = 0.0f; + float m_globalNuclearCapacity = 10.0f; + float m_globalCellCapacity = 1.0f; + bool m_exitAfterMission = false; bool m_codeBattleInit = false; diff --git a/src/level/scene_conditions.cpp b/src/level/scene_conditions.cpp index 41a8a8f0..36e2bdd9 100644 --- a/src/level/scene_conditions.cpp +++ b/src/level/scene_conditions.cpp @@ -82,7 +82,7 @@ bool CObjectCondition::CheckForObject(CObject* obj) } else if (obj->Implements(ObjectInterfaceType::Powered)) { - CObject* powerObj = dynamic_cast(obj)->GetPower(); + CObject* powerObj = dynamic_cast(*obj).GetPower(); if(powerObj != nullptr && powerObj->Implements(ObjectInterfaceType::PowerContainer)) { power = dynamic_cast(powerObj); @@ -98,7 +98,7 @@ bool CObjectCondition::CheckForObject(CObject* obj) Math::Vector oPos; if (IsObjectBeingTransported(obj)) - oPos = dynamic_cast(obj)->GetTransporter()->GetPosition(); + oPos = dynamic_cast(*obj).GetTransporter()->GetPosition(); else oPos = obj->GetPosition(); oPos.y = 0.0f; diff --git a/src/object/auto/autobase.cpp b/src/object/auto/autobase.cpp index d5522c56..7b908095 100644 --- a/src/object/auto/autobase.cpp +++ b/src/object/auto/autobase.cpp @@ -169,7 +169,7 @@ begin: else { assert(pObj->Implements(ObjectInterfaceType::Controllable)); - m_camera->SetType(dynamic_cast(pObj)->GetCameraType()); + m_camera->SetType(dynamic_cast(*pObj).GetCameraType()); } m_main->StartMusic(); @@ -594,7 +594,7 @@ begin: else { assert(pObj->Implements(ObjectInterfaceType::Controllable)); - m_camera->SetType(dynamic_cast(pObj)->GetCameraType()); + m_camera->SetType(dynamic_cast(*pObj).GetCameraType()); } m_sound->Play(SOUND_BOUM, m_object->GetPosition()); m_soundChannel = -1; @@ -1124,7 +1124,7 @@ bool CAutoBase::Abort() else { assert(pObj->Implements(ObjectInterfaceType::Controllable)); - m_camera->SetType(dynamic_cast(pObj)->GetCameraType()); + m_camera->SetType(dynamic_cast(*pObj).GetCameraType()); } m_engine->SetFogStart(m_fogStart); @@ -1248,7 +1248,7 @@ void CAutoBase::FreezeCargo(bool freeze) m_cargoObjects.insert(obj); if ( obj->Implements(ObjectInterfaceType::Movable) ) { - CPhysics* physics = dynamic_cast(obj)->GetPhysics(); + CPhysics* physics = dynamic_cast(*obj).GetPhysics(); physics->SetFreeze(freeze); } } diff --git a/src/object/auto/autodestroyer.cpp b/src/object/auto/autodestroyer.cpp index f30acebf..717a1f2b 100644 --- a/src/object/auto/autodestroyer.cpp +++ b/src/object/auto/autodestroyer.cpp @@ -176,7 +176,7 @@ bool CAutoDestroyer::EventProcess(const Event &event) if ( scrap != nullptr ) { assert(scrap->Implements(ObjectInterfaceType::Destroyable)); - dynamic_cast(scrap)->DestroyObject(DestructionType::Explosion); + dynamic_cast(*scrap).DestroyObject(DestructionType::Explosion); } m_bExplo = true; } diff --git a/src/object/auto/autoegg.cpp b/src/object/auto/autoegg.cpp index a4e7d493..6c0026d8 100644 --- a/src/object/auto/autoegg.cpp +++ b/src/object/auto/autoegg.cpp @@ -74,7 +74,7 @@ void CAutoEgg::DeleteObject(bool all) alien->SetLock(false); if (alien->Implements(ObjectInterfaceType::Programmable)) { - dynamic_cast(alien)->SetActivity(true); // the insect is active + dynamic_cast(*alien).SetActivity(true); // the insect is active } } else @@ -123,7 +123,7 @@ void CAutoEgg::Init() if (alien->Implements(ObjectInterfaceType::Programmable)) { - dynamic_cast(alien)->SetActivity(false); + dynamic_cast(*alien).SetActivity(false); } } @@ -204,7 +204,7 @@ bool CAutoEgg::EventProcess(const Event &event) if ( alien == nullptr ) return true; if (alien->Implements(ObjectInterfaceType::Programmable)) { - dynamic_cast(alien)->SetActivity(false); + dynamic_cast(*alien).SetActivity(false); } m_progress += event.rTime*m_speed; @@ -265,7 +265,7 @@ Error CAutoEgg::IsEnded() alien->SetLock(false); if(alien->Implements(ObjectInterfaceType::Programmable)) { - dynamic_cast(alien)->SetActivity(true); // the insect is active + dynamic_cast(*alien).SetActivity(true); // the insect is active } } diff --git a/src/object/auto/autofactory.cpp b/src/object/auto/autofactory.cpp index 9ba1f3e1..a6061a32 100644 --- a/src/object/auto/autofactory.cpp +++ b/src/object/auto/autofactory.cpp @@ -397,7 +397,7 @@ bool CAutoFactory::EventProcess(const Event &event) if ( vehicle != nullptr ) { assert(vehicle->Implements(ObjectInterfaceType::Movable)); - physics = dynamic_cast(vehicle)->GetPhysics(); + physics = dynamic_cast(*vehicle).GetPhysics(); physics->SetFreeze(false); // can move vehicle->SetLock(false); // vehicle useable @@ -408,7 +408,7 @@ bool CAutoFactory::EventProcess(const Event &event) { if (vehicle->Implements(ObjectInterfaceType::Programmable) && vehicle->Implements(ObjectInterfaceType::ProgramStorage)) { - Program* program = dynamic_cast(vehicle)->AddProgram(); + Program* program = dynamic_cast(*vehicle).AddProgram(); if (boost::regex_match(m_program, boost::regex("[A-Za-z0-9_]+"))) // Public function name? { @@ -424,7 +424,7 @@ bool CAutoFactory::EventProcess(const Event &event) program->script->SendScript(m_program.c_str()); } - dynamic_cast(vehicle)->RunProgram(program); + dynamic_cast(*vehicle).RunProgram(program); } } } @@ -670,7 +670,7 @@ bool CAutoFactory::CreateVehicle() vehicle->SetLock(true); // not usable assert(vehicle->Implements(ObjectInterfaceType::Movable)); - CPhysics* physics = dynamic_cast(vehicle)->GetPhysics(); + CPhysics* physics = dynamic_cast(*vehicle).GetPhysics(); physics->SetFreeze(true); // it doesn't move if (vehicle->Implements(ObjectInterfaceType::ProgramStorage)) diff --git a/src/object/auto/autonuclearplant.cpp b/src/object/auto/autonuclearplant.cpp index 14fd6fc3..2c138c61 100644 --- a/src/object/auto/autonuclearplant.cpp +++ b/src/object/auto/autonuclearplant.cpp @@ -400,7 +400,7 @@ void CAutoNuclearPlant::CreatePower() float powerLevel = 1.0f; CObject* power = CObjectManager::GetInstancePointer()->CreateObject(pos, angle, OBJECT_ATOMIC, powerLevel); - dynamic_cast(power)->SetTransporter(m_object); + dynamic_cast(*power).SetTransporter(m_object); power->SetPosition(Math::Vector(22.0f, 3.0f, 0.0f)); m_object->SetPower(power); } diff --git a/src/object/auto/autopowercaptor.cpp b/src/object/auto/autopowercaptor.cpp index d10a6c88..614c5da4 100644 --- a/src/object/auto/autopowercaptor.cpp +++ b/src/object/auto/autopowercaptor.cpp @@ -269,7 +269,7 @@ void CAutoPowerCaptor::ChargeObject(float rTime) if (obj->Implements(ObjectInterfaceType::Powered)) { - CObject* power = dynamic_cast(obj)->GetPower(); + CObject* power = dynamic_cast(*obj).GetPower(); if ( power != nullptr && power->Implements(ObjectInterfaceType::PowerContainer) ) { CPowerContainerObject* powerContainer = dynamic_cast(power); @@ -285,7 +285,7 @@ void CAutoPowerCaptor::ChargeObject(float rTime) if (obj->Implements(ObjectInterfaceType::Carrier)) { - CObject* power = dynamic_cast(obj)->GetCargo(); + CObject* power = dynamic_cast(*obj).GetCargo(); if ( power != nullptr && power->Implements(ObjectInterfaceType::PowerContainer) ) { CPowerContainerObject* powerContainer = dynamic_cast(power); diff --git a/src/object/auto/autopowerplant.cpp b/src/object/auto/autopowerplant.cpp index 50447573..d0006e2d 100644 --- a/src/object/auto/autopowerplant.cpp +++ b/src/object/auto/autopowerplant.cpp @@ -331,7 +331,7 @@ bool CAutoPowerPlant::EventProcess(const Event &event) cargo->SetScale(1.0f); cargo->SetLock(false); // usable battery - dynamic_cast(cargo)->SetTransporter(m_object); + dynamic_cast(*cargo).SetTransporter(m_object); cargo->SetPosition(Math::Vector(0.0f, 3.0f, 0.0f)); m_object->SetPower(cargo); diff --git a/src/object/auto/autopowerstation.cpp b/src/object/auto/autopowerstation.cpp index 0693c7c7..96bed578 100644 --- a/src/object/auto/autopowerstation.cpp +++ b/src/object/auto/autopowerstation.cpp @@ -138,7 +138,7 @@ bool CAutoPowerStation::EventProcess(const Event &event) { if (vehicle->Implements(ObjectInterfaceType::Powered)) { - CObject* power = dynamic_cast(vehicle)->GetPower(); + CObject* power = dynamic_cast(*vehicle).GetPower(); if ( power != nullptr && power->Implements(ObjectInterfaceType::PowerContainer) ) { CPowerContainerObject* powerContainer = dynamic_cast(power); @@ -158,7 +158,7 @@ bool CAutoPowerStation::EventProcess(const Event &event) if (vehicle->Implements(ObjectInterfaceType::Carrier)) { - CObject* power = dynamic_cast(vehicle)->GetCargo(); + CObject* power = dynamic_cast(*vehicle).GetCargo(); if ( power != nullptr && power->Implements(ObjectInterfaceType::PowerContainer) ) { CPowerContainerObject* powerContainer = dynamic_cast(power); diff --git a/src/object/auto/autorepair.cpp b/src/object/auto/autorepair.cpp index f860f732..39b41d42 100644 --- a/src/object/auto/autorepair.cpp +++ b/src/object/auto/autorepair.cpp @@ -148,7 +148,7 @@ bool CAutoRepair::EventProcess(const Event &event) assert(vehicle->Implements(ObjectInterfaceType::Shielded)); if ( m_progress < 1.0f || - (vehicle != nullptr && dynamic_cast(vehicle)->GetShield() < 1.0f) ) + (vehicle != nullptr && dynamic_cast(*vehicle).GetShield() < 1.0f) ) { if ( vehicle != nullptr ) { @@ -243,9 +243,9 @@ CObject* CAutoRepair::SearchVehicle() { if (obj == m_object) continue; if ( !obj->Implements(ObjectInterfaceType::Shielded) ) continue; - if ( !dynamic_cast(obj)->IsRepairable() ) continue; + if ( !dynamic_cast(*obj).IsRepairable() ) continue; - if ( obj->Implements(ObjectInterfaceType::Movable) && !dynamic_cast(obj)->GetPhysics()->GetLand() ) continue; // in flight? + if ( obj->Implements(ObjectInterfaceType::Movable) && !dynamic_cast(*obj).GetPhysics()->GetLand() ) continue; // in flight? Math::Vector oPos = obj->GetPosition(); float dist = Math::Distance(oPos, sPos); diff --git a/src/object/auto/autotower.cpp b/src/object/auto/autotower.cpp index 65d83277..b509e7e9 100644 --- a/src/object/auto/autotower.cpp +++ b/src/object/auto/autotower.cpp @@ -289,7 +289,7 @@ CObject* CAutoTower::SearchTarget(Math::Vector &impact) { if ( obj->Implements(ObjectInterfaceType::Movable) ) { - CPhysics* physics = dynamic_cast(obj)->GetPhysics(); + CPhysics* physics = dynamic_cast(*obj).GetPhysics(); float speed = fabs(physics->GetLinMotionX(MO_REASPEED)); if ( speed > 20.0f ) continue; // moving too fast? } @@ -302,8 +302,7 @@ CObject* CAutoTower::SearchTarget(Math::Vector &impact) if ( distance > TOWER_SCOPE ) continue; // too far if ( distance < min ) { - min = distance; - best = obj; + min = distance; best = obj; } } if ( best == nullptr ) return nullptr; @@ -327,7 +326,7 @@ Error CAutoTower::GetError() return ERR_TOWER_POWER; // no battery } - if ( dynamic_cast(m_object->GetPower())->GetEnergy() < ENERGY_FIRE ) + if ( dynamic_cast(*m_object->GetPower()).GetEnergy() < ENERGY_FIRE ) { return ERR_TOWER_ENERGY; // not enough energy } diff --git a/src/object/implementation/program_storage_impl.cpp b/src/object/implementation/program_storage_impl.cpp index ced1a4c7..d5a914ff 100644 --- a/src/object/implementation/program_storage_impl.cpp +++ b/src/object/implementation/program_storage_impl.cpp @@ -270,7 +270,7 @@ void CProgramStorageObjectImpl::LoadAllProgramsForLevel(CLevelParserLine* levelS if (m_object->Implements(ObjectInterfaceType::Programmable) && i == run) { - dynamic_cast(m_object)->RunProgram(program); + dynamic_cast(*m_object).RunProgram(program); } } else @@ -327,7 +327,7 @@ void CProgramStorageObjectImpl::SaveAllProgramsForSavedScene(CLevelParserLine* l } if (m_programStorageIndex < 0) return; - if (!m_object->Implements(ObjectInterfaceType::Controllable) || !dynamic_cast(m_object)->GetSelectable() || m_object->GetType() == OBJECT_HUMAN) return; + if (!m_object->Implements(ObjectInterfaceType::Controllable) || !dynamic_cast(*m_object).GetSelectable() || m_object->GetType() == OBJECT_HUMAN) return; GetLogger()->Debug("Saving saved scene programs to '%s/prog%.3d___.txt'\n", levelSource.c_str(), m_programStorageIndex); for (unsigned int i = 0; i < m_program.size(); i++) @@ -379,7 +379,7 @@ void CProgramStorageObjectImpl::LoadAllProgramsForSavedScene(CLevelParserLine* l if (m_object->Implements(ObjectInterfaceType::Programmable) && i == run) { - dynamic_cast(m_object)->RunProgram(program); + dynamic_cast(*m_object).RunProgram(program); } } } @@ -403,7 +403,7 @@ void CProgramStorageObjectImpl::LoadAllProgramsForSavedScene(CLevelParserLine* l if (m_object->Implements(ObjectInterfaceType::Programmable) && i == run) { - dynamic_cast(m_object)->RunProgram(program); + dynamic_cast(*m_object).RunProgram(program); } } } diff --git a/src/object/implementation/programmable_impl.cpp b/src/object/implementation/programmable_impl.cpp index f21bf4b7..8748f040 100644 --- a/src/object/implementation/programmable_impl.cpp +++ b/src/object/implementation/programmable_impl.cpp @@ -68,7 +68,7 @@ bool CProgrammableObjectImpl::EventProcess(const Event &event) { if (event.type == EVENT_FRAME) { - if ( m_object->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(m_object)->IsDying() && IsProgram() ) + if ( m_object->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(*m_object).IsDying() && IsProgram() ) { StopProgram(); } @@ -113,7 +113,7 @@ void CProgrammableObjectImpl::RunProgram(Program* program) { m_currentProgram = program; // start new program m_object->UpdateInterface(); - if (m_object->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(m_object)->GetTrainer()) + if (m_object->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(*m_object).GetTrainer()) CRobotMain::GetInstancePointer()->StartMissionTimer(); } } @@ -155,7 +155,7 @@ bool CProgrammableObjectImpl::ReadStack(std::istream &istr) { if (m_object->Implements(ObjectInterfaceType::ProgramStorage)) { - int count = static_cast(dynamic_cast(m_object)->GetProgramCount()); + int count = static_cast(dynamic_cast(*m_object).GetProgramCount()); if (!(op < count)) { GetLogger()->Info("Object program count: %i\n", count); @@ -163,7 +163,7 @@ bool CProgrammableObjectImpl::ReadStack(std::istream &istr) return false; } - m_currentProgram = dynamic_cast(m_object)->GetProgram(op); + m_currentProgram = dynamic_cast(*m_object).GetProgram(op); if (!m_currentProgram->script->ReadStack(istr)) { GetLogger()->Error("Restore state failed at program index: %i\n", op); @@ -203,7 +203,7 @@ bool CProgrammableObjectImpl::WriteStack(std::ostream &ostr) op = -1; if (m_object->Implements(ObjectInterfaceType::ProgramStorage)) { - op = dynamic_cast(m_object)->GetProgramIndex(m_currentProgram); + op = dynamic_cast(*m_object).GetProgramIndex(m_currentProgram); } if (!CBot::WriteShort(ostr, op)) return false; @@ -266,7 +266,7 @@ void CProgrammableObjectImpl::TraceRecordFrame() assert(m_object->Implements(ObjectInterfaceType::TraceDrawing)); CTraceDrawingObject* traceDrawing = dynamic_cast(m_object); - CPhysics* physics = dynamic_cast(m_object)->GetPhysics(); + CPhysics* physics = dynamic_cast(*m_object).GetPhysics(); speed = physics->GetLinMotionX(MO_REASPEED); if ( speed > 0.0f ) oper = TO_ADVANCE; @@ -353,7 +353,7 @@ void CProgrammableObjectImpl::TraceRecordStop() buffer << "}\n"; assert(m_object->Implements(ObjectInterfaceType::ProgramStorage)); - Program* prog = dynamic_cast(m_object)->AddProgram(); + Program* prog = dynamic_cast(*m_object).AddProgram(); prog->script->SendScript(buffer.str().c_str()); } diff --git a/src/object/interface/carrier_object.h b/src/object/interface/carrier_object.h index 857ff0bf..2cbf3d73 100644 --- a/src/object/interface/carrier_object.h +++ b/src/object/interface/carrier_object.h @@ -51,5 +51,5 @@ public: inline bool IsObjectCarryingCargo(CObject* obj) { return obj->Implements(ObjectInterfaceType::Carrier) && - dynamic_cast(obj)->IsCarryingCargo(); + dynamic_cast(*obj).IsCarryingCargo(); } diff --git a/src/object/interface/powered_object.h b/src/object/interface/powered_object.h index b1652e47..3aea3296 100644 --- a/src/object/interface/powered_object.h +++ b/src/object/interface/powered_object.h @@ -61,10 +61,10 @@ inline float GetObjectEnergy(CObject* object) if (object->Implements(ObjectInterfaceType::Powered)) { - CObject* power = dynamic_cast(object)->GetPower(); + CObject* power = dynamic_cast(*object).GetPower(); if (power != nullptr && power->Implements(ObjectInterfaceType::PowerContainer)) { - energy = dynamic_cast(power)->GetEnergy(); + energy = dynamic_cast(*power).GetEnergy(); } } @@ -77,10 +77,10 @@ inline float GetObjectEnergyLevel(CObject* object) if (object->Implements(ObjectInterfaceType::Powered)) { - CObject* power = dynamic_cast(object)->GetPower(); + CObject* power = dynamic_cast(*object).GetPower(); if (power != nullptr && power->Implements(ObjectInterfaceType::PowerContainer)) { - energy = dynamic_cast(power)->GetEnergyLevel(); + energy = dynamic_cast(*power).GetEnergyLevel(); } } @@ -90,5 +90,5 @@ inline float GetObjectEnergyLevel(CObject* object) inline bool ObjectHasPowerCell(CObject* object) { return object->Implements(ObjectInterfaceType::Powered) && - dynamic_cast(object)->GetPower() != nullptr; + dynamic_cast(*object).GetPower() != nullptr; } diff --git a/src/object/interface/transportable_object.h b/src/object/interface/transportable_object.h index a45d9e0c..38e88dc5 100644 --- a/src/object/interface/transportable_object.h +++ b/src/object/interface/transportable_object.h @@ -54,5 +54,5 @@ public: inline bool IsObjectBeingTransported(CObject* obj) { return obj->Implements(ObjectInterfaceType::Transportable) && - dynamic_cast(obj)->IsBeingTransported(); + dynamic_cast(*obj).IsBeingTransported(); } diff --git a/src/object/motion/motionant.cpp b/src/object/motion/motionant.cpp index 889e6d96..2b4cf2c7 100644 --- a/src/object/motion/motionant.cpp +++ b/src/object/motion/motionant.cpp @@ -429,7 +429,7 @@ bool CMotionAnt::EventFrame(const Event &event) assert(m_object->Implements(ObjectInterfaceType::Destroyable)); if ( dynamic_cast(m_object)->GetDying() == DeathType::Burning ) // burning? { - if ( dynamic_cast(m_object)->GetFixed() ) + if ( dynamic_cast(*m_object).GetFixed() ) { m_actionType = MAS_BURN; } @@ -724,7 +724,7 @@ bool CMotionAnt::EventFrame(const Event &event) if ( m_progress >= 1.0f ) { SetAction(-1); - dynamic_cast(m_object)->SetFixed(false); // moving again + dynamic_cast(*m_object).SetFixed(false); // moving again } } else diff --git a/src/object/motion/motionspider.cpp b/src/object/motion/motionspider.cpp index ec8af342..c65807f1 100644 --- a/src/object/motion/motionspider.cpp +++ b/src/object/motion/motionspider.cpp @@ -364,7 +364,7 @@ bool CMotionSpider::EventFrame(const Event &event) assert(m_object->Implements(ObjectInterfaceType::Destroyable)); if (dynamic_cast(m_object)->GetDying() == DeathType::Burning ) // burning? { - if ( dynamic_cast(m_object)->GetFixed() ) + if ( dynamic_cast(*m_object).GetFixed() ) { m_actionType = MSS_BURN; } @@ -648,7 +648,7 @@ bool CMotionSpider::EventFrame(const Event &event) if ( m_progress >= 1.0f ) { SetAction(-1); - dynamic_cast(m_object)->SetFixed(false); // moving again + dynamic_cast(*m_object).SetFixed(false); // moving again } } else diff --git a/src/object/motion/motionvehicle.cpp b/src/object/motion/motionvehicle.cpp index 4f2a09f1..b9719c34 100644 --- a/src/object/motion/motionvehicle.cpp +++ b/src/object/motion/motionvehicle.cpp @@ -1075,7 +1075,7 @@ void CMotionVehicle::Create(Math::Vector pos, float angle, ObjectType type, powerCell->SetPosition(powerCellPos); powerCell->SetRotation(Math::Vector(0.0f, powerCellAngle, 0.0f)); - dynamic_cast(powerCell)->SetTransporter(m_object); + dynamic_cast(*powerCell).SetTransporter(m_object); assert(m_object->Implements(ObjectInterfaceType::Powered)); m_object->SetPower(powerCell); } diff --git a/src/object/object_manager.cpp b/src/object/object_manager.cpp index 3da2478b..58c7475e 100644 --- a/src/object/object_manager.cpp +++ b/src/object/object_manager.cpp @@ -142,6 +142,8 @@ CObject* CObjectManager::CreateObject(ObjectCreateParams params) } } + params.power = ClampPower(params.type,params.power); + assert(m_objects.find(params.id) == m_objects.end()); auto objectUPtr = m_objectFactory->CreateObject(params); @@ -163,10 +165,20 @@ CObject* CObjectManager::CreateObject(Math::Vector pos, float angle, ObjectType params.angle = angle; params.type = type; params.power = power; - return CreateObject(params); } +float CObjectManager::ClampPower(ObjectType type, float power) +{ + float min = 0; + float max = 100; + if (type == OBJECT_POWER || type == OBJECT_ATOMIC) + { + max = 1; + } + return Math::Clamp(power, min, max); +} + std::vector CObjectManager::GetObjectsOfTeam(int team) { std::vector result; @@ -205,7 +217,7 @@ void CObjectManager::DestroyTeam(int team, DestructionType destructionType) { if (object->Implements(ObjectInterfaceType::Destroyable)) { - dynamic_cast(object)->DestroyObject(destructionType); + dynamic_cast(*object).DestroyObject(destructionType); } else { @@ -351,7 +363,7 @@ std::vector CObjectManager::RadarAll(CObject* pThis, Math::Vector this { if ( pObj->Implements(ObjectInterfaceType::Movable) ) { - CPhysics* physics = dynamic_cast(pObj)->GetPhysics(); + CPhysics* physics = dynamic_cast(*pObj).GetPhysics(); if ( physics != nullptr ) { if ( !physics->GetLand() ) continue; @@ -361,7 +373,7 @@ std::vector CObjectManager::RadarAll(CObject* pThis, Math::Vector this if ( filter_flying == FILTER_ONLYFLYING ) { if ( !pObj->Implements(ObjectInterfaceType::Movable) ) continue; - CPhysics* physics = dynamic_cast(pObj)->GetPhysics(); + CPhysics* physics = dynamic_cast(*pObj).GetPhysics(); if ( physics == nullptr ) continue; if ( physics->GetLand() ) continue; } diff --git a/src/object/object_manager.h b/src/object/object_manager.h index b5296156..3ae55c84 100644 --- a/src/object/object_manager.h +++ b/src/object/object_manager.h @@ -303,6 +303,8 @@ public: //@} private: + //! Prevents creation of overcharged power cells + float ClampPower(ObjectType type, float power); void CleanRemovedObjectsIfNeeded(); private: diff --git a/src/object/object_type.cpp b/src/object/object_type.cpp new file mode 100644 index 00000000..ca452bc0 --- /dev/null +++ b/src/object/object_type.cpp @@ -0,0 +1,236 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#include "object/object_type.h" + +#include + +bool IsValidObjectTypeId(int id) +{ + static const std::unordered_set validIds{ + OBJECT_NULL, + OBJECT_PORTICO, + OBJECT_BASE, + OBJECT_DERRICK, + OBJECT_FACTORY, + OBJECT_STATION, + OBJECT_CONVERT, + OBJECT_REPAIR, + OBJECT_TOWER, + OBJECT_NEST, + OBJECT_RESEARCH, + OBJECT_RADAR, + OBJECT_ENERGY, + OBJECT_LABO, + OBJECT_NUCLEAR, + OBJECT_START, + OBJECT_END, + OBJECT_INFO, + OBJECT_PARA, + OBJECT_TARGET1, + OBJECT_TARGET2, + OBJECT_SAFE, + OBJECT_HUSTON, + OBJECT_DESTROYER, + OBJECT_STONE, + OBJECT_URANIUM, + OBJECT_METAL, + OBJECT_POWER, + OBJECT_ATOMIC, + OBJECT_BULLET, + OBJECT_BBOX, + OBJECT_TNT, + OBJECT_MARKPOWER, + OBJECT_MARKSTONE, + OBJECT_MARKURANIUM, + OBJECT_MARKKEYa, + OBJECT_MARKKEYb, + OBJECT_MARKKEYc, + OBJECT_MARKKEYd, + OBJECT_BOMB, + OBJECT_WINFIRE, + OBJECT_SHOW, + OBJECT_BAG, + OBJECT_PLANT0, + OBJECT_PLANT1, + OBJECT_PLANT2, + OBJECT_PLANT3, + OBJECT_PLANT4, + OBJECT_PLANT5, + OBJECT_PLANT6, + OBJECT_PLANT7, + OBJECT_PLANT8, + OBJECT_PLANT9, + OBJECT_PLANT10, + OBJECT_PLANT11, + OBJECT_PLANT12, + OBJECT_PLANT13, + OBJECT_PLANT14, + OBJECT_PLANT15, + OBJECT_PLANT16, + OBJECT_PLANT17, + OBJECT_PLANT18, + OBJECT_PLANT19, + OBJECT_TREE0, + OBJECT_TREE1, + OBJECT_TREE2, + OBJECT_TREE3, + OBJECT_TREE4, + OBJECT_TREE5, + OBJECT_MOBILEwt, + OBJECT_MOBILEtt, + OBJECT_MOBILEft, + OBJECT_MOBILEit, + OBJECT_MOBILErp, + OBJECT_MOBILEst, + OBJECT_MOBILEwa, + OBJECT_MOBILEta, + OBJECT_MOBILEfa, + OBJECT_MOBILEia, + OBJECT_MOBILEwc, + OBJECT_MOBILEtc, + OBJECT_MOBILEfc, + OBJECT_MOBILEic, + OBJECT_MOBILEwi, + OBJECT_MOBILEti, + OBJECT_MOBILEfi, + OBJECT_MOBILEii, + OBJECT_MOBILEws, + OBJECT_MOBILEts, + OBJECT_MOBILEfs, + OBJECT_MOBILEis, + OBJECT_MOBILErt, + OBJECT_MOBILErc, + OBJECT_MOBILErr, + OBJECT_MOBILErs, + OBJECT_MOBILEsa, + OBJECT_MOBILEtg, + OBJECT_MOBILEdr, + OBJECT_CONTROLLER, + OBJECT_MOBILEwb, + OBJECT_MOBILEtb, + OBJECT_MOBILEfb, + OBJECT_MOBILEib, + OBJECT_MOBILEpr, + OBJECT_WAYPOINT, + OBJECT_FLAGb, + OBJECT_FLAGr, + OBJECT_FLAGg, + OBJECT_FLAGy, + OBJECT_FLAGv, + OBJECT_KEYa, + OBJECT_KEYb, + OBJECT_KEYc, + OBJECT_KEYd, + OBJECT_HUMAN, + OBJECT_TOTO, + OBJECT_TECH, + OBJECT_BARRIER0, + OBJECT_BARRIER1, + OBJECT_BARRIER2, + OBJECT_BARRIER3, + OBJECT_BARRICADE0, + OBJECT_BARRICADE1, + OBJECT_MOTHER, + OBJECT_EGG, + OBJECT_ANT, + OBJECT_SPIDER, + OBJECT_BEE, + OBJECT_WORM, + OBJECT_RUINmobilew1, + OBJECT_RUINmobilew2, + OBJECT_RUINmobilet1, + OBJECT_RUINmobilet2, + OBJECT_RUINmobiler1, + OBJECT_RUINmobiler2, + OBJECT_RUINfactory, + OBJECT_RUINdoor, + OBJECT_RUINsupport, + OBJECT_RUINradar, + OBJECT_RUINconvert, + OBJECT_RUINbase, + OBJECT_RUINhead, + OBJECT_TEEN0, + OBJECT_TEEN1, + OBJECT_TEEN2, + OBJECT_TEEN3, + OBJECT_TEEN4, + OBJECT_TEEN5, + OBJECT_TEEN6, + OBJECT_TEEN7, + OBJECT_TEEN8, + OBJECT_TEEN9, + OBJECT_TEEN10, + OBJECT_TEEN11, + OBJECT_TEEN12, + OBJECT_TEEN13, + OBJECT_TEEN14, + OBJECT_TEEN15, + OBJECT_TEEN16, + OBJECT_TEEN17, + OBJECT_TEEN18, + OBJECT_TEEN19, + OBJECT_TEEN20, + OBJECT_TEEN21, + OBJECT_TEEN22, + OBJECT_TEEN23, + OBJECT_TEEN24, + OBJECT_TEEN25, + OBJECT_TEEN26, + OBJECT_TEEN27, + OBJECT_TEEN28, + OBJECT_TEEN29, + OBJECT_TEEN30, + OBJECT_TEEN31, + OBJECT_TEEN32, + OBJECT_TEEN33, + OBJECT_TEEN34, + OBJECT_TEEN35, + OBJECT_TEEN36, + OBJECT_TEEN37, + OBJECT_TEEN38, + OBJECT_TEEN39, + OBJECT_TEEN40, + OBJECT_TEEN41, + OBJECT_TEEN42, + OBJECT_TEEN43, + OBJECT_TEEN44, + OBJECT_QUARTZ0, + OBJECT_QUARTZ1, + OBJECT_QUARTZ2, + OBJECT_QUARTZ3, + OBJECT_ROOT0, + OBJECT_ROOT1, + OBJECT_ROOT2, + OBJECT_ROOT3, + OBJECT_ROOT4, + OBJECT_ROOT5, + OBJECT_MUSHROOM1, + OBJECT_MUSHROOM2, + OBJECT_APOLLO1, + OBJECT_APOLLO2, + OBJECT_APOLLO3, + OBJECT_APOLLO4, + OBJECT_APOLLO5, + OBJECT_HOME1, + + OBJECT_MAX + }; + return validIds.count(id); +} diff --git a/src/object/object_type.h b/src/object/object_type.h index d4a8f062..7f78e304 100644 --- a/src/object/object_type.h +++ b/src/object/object_type.h @@ -248,3 +248,5 @@ struct ObjectTypeHash return std::hash()(t); } }; + +bool IsValidObjectTypeId(int id); diff --git a/src/object/old_object.cpp b/src/object/old_object.cpp index 9de9ed44..7dedda8a 100644 --- a/src/object/old_object.cpp +++ b/src/object/old_object.cpp @@ -285,8 +285,8 @@ void COldObject::DeleteObject(bool bAll) { if (m_power->Implements(ObjectInterfaceType::Old)) { - dynamic_cast(m_power)->SetTransporter(nullptr); - dynamic_cast(m_power)->DeleteObject(bAll); + dynamic_cast(*m_power).SetTransporter(nullptr); + dynamic_cast(*m_power).DeleteObject(bAll); } m_power = nullptr; } @@ -294,8 +294,8 @@ void COldObject::DeleteObject(bool bAll) { if (m_cargo->Implements(ObjectInterfaceType::Old)) { - dynamic_cast(m_cargo)->SetTransporter(nullptr); - dynamic_cast(m_cargo)->DeleteObject(bAll); + dynamic_cast(*m_cargo).SetTransporter(nullptr); + dynamic_cast(*m_cargo).DeleteObject(bAll); } m_cargo = nullptr; } @@ -2505,7 +2505,7 @@ float COldObject::GetAbsTime() float COldObject::GetCapacity() { - return m_type == OBJECT_ATOMIC ? 10.0f : 1.0f; + return m_type == OBJECT_ATOMIC ? m_main->GetGlobalNuclearCapacity() : m_main->GetGlobalCellCapacity() ; } bool COldObject::IsRechargeable() diff --git a/src/object/task/taskfire.cpp b/src/object/task/taskfire.cpp index 72247447..87730880 100644 --- a/src/object/task/taskfire.cpp +++ b/src/object/task/taskfire.cpp @@ -317,7 +317,7 @@ Error CTaskFire::Start(float delay) CObject* power = dynamic_cast(m_object)->GetPower(); if (power == nullptr || !power->Implements(ObjectInterfaceType::PowerContainer)) return ERR_FIRE_ENERGY; - energy = dynamic_cast(power)->GetEnergy(); + energy = dynamic_cast(*power).GetEnergy(); if ( m_bOrganic ) fire = m_delay*ENERGY_FIREi; else if ( m_bRay ) fire = m_delay*ENERGY_FIREr; else fire = m_delay*ENERGY_FIRE; diff --git a/src/object/task/taskfireant.cpp b/src/object/task/taskfireant.cpp index a1ee384f..99bc7d0b 100644 --- a/src/object/task/taskfireant.cpp +++ b/src/object/task/taskfireant.cpp @@ -60,7 +60,7 @@ bool CTaskFireAnt::EventProcess(const Event &event) if ( event.type != EVENT_FRAME ) return true; if ( m_bError ) return false; - if ( dynamic_cast(m_object)->GetFixed() ) // insect on its back? + if ( dynamic_cast(*m_object).GetFixed() ) // insect on its back? { m_bError = true; return false; @@ -100,7 +100,7 @@ Error CTaskFireAnt::Start(Math::Vector impact) if ( type != OBJECT_ANT ) return ERR_WRONG_BOT; // Insect on its back? - if ( dynamic_cast(m_object)->GetFixed() ) return ERR_WRONG_BOT; + if ( dynamic_cast(*m_object).GetFixed() ) return ERR_WRONG_BOT; m_physics->SetMotorSpeed(Math::Vector(0.0f, 0.0f, 0.0f)); @@ -130,7 +130,7 @@ Error CTaskFireAnt::IsEnded() if ( m_engine->GetPause() ) return ERR_CONTINUE; if ( m_bError ) return ERR_STOP; - if ( dynamic_cast(m_object)->GetFixed() ) return ERR_STOP; // insect on its back? + if ( dynamic_cast(*m_object).GetFixed() ) return ERR_STOP; // insect on its back? if ( m_phase == TFA_TURN ) // rotation ? { diff --git a/src/object/task/taskgoto.cpp b/src/object/task/taskgoto.cpp index 1224b391..d2e64667 100644 --- a/src/object/task/taskgoto.cpp +++ b/src/object/task/taskgoto.cpp @@ -1202,7 +1202,7 @@ bool CTaskGoto::AdjustTarget(CObject* pObj, Math::Vector &pos, float &distance) type == OBJECT_MOBILEdr ) { assert(pObj->Implements(ObjectInterfaceType::Powered)); - pos = dynamic_cast(pObj)->GetPowerPosition(); + pos = dynamic_cast(*pObj).GetPowerPosition(); pos.x -= TAKE_DIST+TAKE_DIST_OTHER+distance; mat = pObj->GetWorldMatrix(0); pos = Transform(*mat, pos); diff --git a/src/object/task/taskmanip.cpp b/src/object/task/taskmanip.cpp index ff55962f..aa62ef9e 100644 --- a/src/object/task/taskmanip.cpp +++ b/src/object/task/taskmanip.cpp @@ -304,8 +304,8 @@ Error CTaskManip::Start(TaskManipOrder order, TaskManipArm arm) assert(other->Implements(ObjectInterfaceType::Transportable)); m_object->SetCargo(other); // takes the ball - dynamic_cast(other)->SetTransporter(m_object); - dynamic_cast(other)->SetTransporterPart(0); // taken with the base + dynamic_cast(*other).SetTransporter(m_object); + dynamic_cast(*other).SetTransporterPart(0); // taken with the base other->SetPosition(Math::Vector(0.0f, -3.0f, 0.0f)); } else @@ -314,7 +314,7 @@ Error CTaskManip::Start(TaskManipOrder order, TaskManipArm arm) assert(other->Implements(ObjectInterfaceType::Transportable)); m_object->SetCargo(nullptr); // lick the ball - dynamic_cast(other)->SetTransporter(nullptr); + dynamic_cast(*other).SetTransporter(nullptr); pos = m_object->GetPosition(); pos.y -= 3.0f; other->SetPosition(pos); @@ -903,7 +903,7 @@ CObject* CTaskManip::SearchOtherObject(bool bAdvance, Math::Vector &pos, ObjectType type = pObj->GetType(); if ( !pObj->Implements(ObjectInterfaceType::Powered) ) continue; - CObject* power = dynamic_cast(pObj)->GetPower(); + CObject* power = dynamic_cast(*pObj).GetPower(); if (power != nullptr) { if (power->GetLock()) continue; @@ -911,7 +911,7 @@ CObject* CTaskManip::SearchOtherObject(bool bAdvance, Math::Vector &pos, } mat = pObj->GetWorldMatrix(0); - Math::Vector oPos = Transform(*mat, dynamic_cast(pObj)->GetPowerPosition()); + Math::Vector oPos = Transform(*mat, dynamic_cast(*pObj).GetPowerPosition()); oAngle = pObj->GetRotationY(); if ( type == OBJECT_TOWER || @@ -946,7 +946,7 @@ CObject* CTaskManip::SearchOtherObject(bool bAdvance, Math::Vector &pos, angle = Math::RotateAngle(oPos.x-iPos.x, iPos.z-oPos.z); // CW ! if ( Math::TestAngle(angle, iAngle-aLimit, iAngle+aLimit) ) { - Math::Vector powerPos = dynamic_cast(pObj)->GetPowerPosition(); + Math::Vector powerPos = dynamic_cast(*pObj).GetPowerPosition(); height = powerPos.y; pos = oPos; return pObj; @@ -974,8 +974,8 @@ bool CTaskManip::TransporterTakeObject() if ( m_object->GetType() == OBJECT_HUMAN || m_object->GetType() == OBJECT_TECH ) { - dynamic_cast(cargo)->SetTransporter(m_object); - dynamic_cast(cargo)->SetTransporterPart(4); // takes with the hand + dynamic_cast(*cargo).SetTransporter(m_object); + dynamic_cast(*cargo).SetTransporterPart(4); // takes with the hand cargo->SetPosition(Math::Vector(1.7f, -0.5f, 1.1f)); cargo->SetRotationY(0.1f); @@ -984,8 +984,8 @@ bool CTaskManip::TransporterTakeObject() } else if ( m_bSubm ) { - dynamic_cast(cargo)->SetTransporter(m_object); - dynamic_cast(cargo)->SetTransporterPart(2); // takes with the right claw + dynamic_cast(*cargo).SetTransporter(m_object); + dynamic_cast(*cargo).SetTransporterPart(2); // takes with the right claw Math::Vector pos = Math::Vector(1.1f, -1.0f, 1.0f); // relative cargo->SetPosition(pos); @@ -995,8 +995,8 @@ bool CTaskManip::TransporterTakeObject() } else { - dynamic_cast(cargo)->SetTransporter(m_object); - dynamic_cast(cargo)->SetTransporterPart(3); // takes with the hand + dynamic_cast(*cargo).SetTransporter(m_object); + dynamic_cast(*cargo).SetTransporterPart(3); // takes with the hand Math::Vector pos = Math::Vector(4.7f, 0.0f, 0.0f); // relative to the hand (lem4) cargo->SetPosition(pos); @@ -1020,8 +1020,8 @@ bool CTaskManip::TransporterTakeObject() if ( m_bSubm ) { - dynamic_cast(cargo)->SetTransporter(m_object); - dynamic_cast(cargo)->SetTransporterPart(2); // takes with the right claw + dynamic_cast(*cargo).SetTransporter(m_object); + dynamic_cast(*cargo).SetTransporterPart(2); // takes with the right claw pos = Math::Vector(1.1f, -1.0f, 1.0f); // relative cargo->SetPosition(pos); @@ -1031,8 +1031,8 @@ bool CTaskManip::TransporterTakeObject() } else { - dynamic_cast(cargo)->SetTransporter(m_object); - dynamic_cast(cargo)->SetTransporterPart(3); // takes with the hand + dynamic_cast(*cargo).SetTransporter(m_object); + dynamic_cast(*cargo).SetTransporterPart(3); // takes with the hand pos = Math::Vector(4.7f, 0.0f, 0.0f); // relative to the hand (lem4) cargo->SetPosition(pos); @@ -1054,8 +1054,8 @@ bool CTaskManip::TransporterTakeObject() m_cargoType = cargo->GetType(); - dynamic_cast(cargo)->SetTransporter(m_object); - dynamic_cast(cargo)->SetTransporterPart(3); // takes with the hand + dynamic_cast(*cargo).SetTransporter(m_object); + dynamic_cast(*cargo).SetTransporterPart(3); // takes with the hand pos = Math::Vector(4.7f, 0.0f, 0.0f); // relative to the hand (lem4) cargo->SetPosition(pos); @@ -1080,7 +1080,7 @@ bool CTaskManip::TransporterTakeObject() cargo->SetRotationX(0.0f); cargo->SetRotationZ(Math::PI/2.0f); cargo->SetRotationY(0.0f); - dynamic_cast(cargo)->SetTransporterPart(3); // takes with the hand + dynamic_cast(*cargo).SetTransporterPart(3); // takes with the hand m_object->SetPower(nullptr); m_object->SetCargo(cargo); // takes @@ -1094,15 +1094,15 @@ bool CTaskManip::TransporterTakeObject() if (other == nullptr) return false; assert(other->Implements(ObjectInterfaceType::Powered)); - CObject* cargo = dynamic_cast(other)->GetPower(); + CObject* cargo = dynamic_cast(*other).GetPower(); if (cargo == nullptr) return false; // the other does not have a battery? assert(cargo->Implements(ObjectInterfaceType::Transportable)); m_cargoType = cargo->GetType(); - dynamic_cast(other)->SetPower(nullptr); - dynamic_cast(cargo)->SetTransporter(m_object); - dynamic_cast(cargo)->SetTransporterPart(3); // takes with the hand + dynamic_cast(*other).SetPower(nullptr); + dynamic_cast(*cargo).SetTransporter(m_object); + dynamic_cast(*cargo).SetTransporterPart(3); // takes with the hand pos = Math::Vector(4.7f, 0.0f, 0.0f); // relative to the hand (lem4) cargo->SetPosition(pos); @@ -1137,7 +1137,7 @@ bool CTaskManip::TransporterDeposeObject() cargo->SetRotationZ(0.0f); cargo->FloorAdjust(); // plate well on the ground - dynamic_cast(cargo)->SetTransporter(nullptr); + dynamic_cast(*cargo).SetTransporter(nullptr); m_object->SetCargo(nullptr); // deposit } @@ -1157,7 +1157,7 @@ bool CTaskManip::TransporterDeposeObject() cargo->SetRotationX(0.0f); cargo->SetRotationZ(0.0f); - dynamic_cast(cargo)->SetTransporter(nullptr); + dynamic_cast(*cargo).SetTransporter(nullptr); m_object->SetCargo(nullptr); // deposit } @@ -1172,8 +1172,8 @@ bool CTaskManip::TransporterDeposeObject() if (m_object->GetPower() != nullptr) return false; - dynamic_cast(cargo)->SetTransporter(m_object); - dynamic_cast(cargo)->SetTransporterPart(0); // carried by the base + dynamic_cast(*cargo).SetTransporter(m_object); + dynamic_cast(*cargo).SetTransporterPart(0); // carried by the base cargo->SetPosition(m_object->GetPowerPosition()); cargo->SetRotationY(0.0f); @@ -1193,7 +1193,7 @@ bool CTaskManip::TransporterDeposeObject() if (other == nullptr) return false; assert(other->Implements(ObjectInterfaceType::Powered)); - CObject* cargo = dynamic_cast(other)->GetPower(); + CObject* cargo = dynamic_cast(*other).GetPower(); if (cargo != nullptr) return false; // the other already has a battery? cargo = m_object->GetCargo(); @@ -1202,14 +1202,14 @@ bool CTaskManip::TransporterDeposeObject() m_cargoType = cargo->GetType(); - dynamic_cast(other)->SetPower(cargo); - dynamic_cast(cargo)->SetTransporter(other); + dynamic_cast(*other).SetPower(cargo); + dynamic_cast(*cargo).SetTransporter(other); - cargo->SetPosition(dynamic_cast(other)->GetPowerPosition()); + cargo->SetPosition(dynamic_cast(*other).GetPowerPosition()); cargo->SetRotationY(0.0f); cargo->SetRotationX(0.0f); cargo->SetRotationZ(0.0f); - dynamic_cast(cargo)->SetTransporterPart(0); // carried by the base + dynamic_cast(*cargo).SetTransporterPart(0); // carried by the base m_object->SetCargo(nullptr); // deposit } diff --git a/src/object/task/taskrecover.cpp b/src/object/task/taskrecover.cpp index e039e7ae..f56503d5 100644 --- a/src/object/task/taskrecover.cpp +++ b/src/object/task/taskrecover.cpp @@ -194,7 +194,7 @@ Error CTaskRecover::Start() CObject* power = dynamic_cast(m_object)->GetPower(); if (power == nullptr || !power->Implements(ObjectInterfaceType::PowerContainer)) return ERR_RECOVER_ENERGY; - float energy = dynamic_cast(power)->GetEnergy(); + float energy = dynamic_cast(*power).GetEnergy(); if ( energy < ENERGY_RECOVER+0.05f ) return ERR_RECOVER_ENERGY; Math::Matrix* mat = m_object->GetWorldMatrix(0); diff --git a/src/object/task/taskshield.cpp b/src/object/task/taskshield.cpp index ad39a688..916a5158 100644 --- a/src/object/task/taskshield.cpp +++ b/src/object/task/taskshield.cpp @@ -309,7 +309,7 @@ Error CTaskShield::Start(TaskShieldMode mode, float delay) CObject* power = m_object->GetPower(); if (power == nullptr || !power->Implements(ObjectInterfaceType::PowerContainer)) return ERR_SHIELD_ENERGY; - float energy = dynamic_cast(power)->GetEnergy(); + float energy = dynamic_cast(*power).GetEnergy(); if ( energy == 0.0f ) return ERR_SHIELD_ENERGY; Math::Matrix* mat = m_object->GetWorldMatrix(0); diff --git a/src/object/task/taskspiderexplo.cpp b/src/object/task/taskspiderexplo.cpp index ed1b8ef7..2acab5fd 100644 --- a/src/object/task/taskspiderexplo.cpp +++ b/src/object/task/taskspiderexplo.cpp @@ -57,7 +57,7 @@ bool CTaskSpiderExplo::EventProcess(const Event &event) if ( event.type != EVENT_FRAME ) return true; // Momentarily stationary object (ant on the back)? - if ( dynamic_cast(m_object)->GetFixed() ) + if ( dynamic_cast(*m_object).GetFixed() ) { m_bError = true; return true; diff --git a/src/object/task/tasktake.cpp b/src/object/task/tasktake.cpp index c1c0de9e..6f7360bd 100644 --- a/src/object/task/tasktake.cpp +++ b/src/object/task/tasktake.cpp @@ -131,9 +131,9 @@ Error CTaskTake::Start() CObject* other = SearchFriendObject(oAngle, 1.5f, Math::PI*0.50f); if (other != nullptr) assert(other->Implements(ObjectInterfaceType::Powered)); - if (other != nullptr && dynamic_cast(other)->GetPower() != nullptr) + if (other != nullptr && dynamic_cast(*other).GetPower() != nullptr) { - CObject* power = dynamic_cast(other)->GetPower(); + CObject* power = dynamic_cast(*other).GetPower(); type = power->GetType(); if ( type == OBJECT_URANIUM ) return ERR_MANIP_RADIO; assert(power->Implements(ObjectInterfaceType::Transportable)); @@ -161,7 +161,7 @@ Error CTaskTake::Start() CObject* other = SearchFriendObject(oAngle, 1.5f, Math::PI*0.50f); if (other != nullptr) assert(other->Implements(ObjectInterfaceType::Powered)); - if (other != nullptr && dynamic_cast(other)->GetPower() == nullptr ) + if (other != nullptr && dynamic_cast(*other).GetPower() == nullptr ) { //? m_camera->StartCentering(m_object, Math::PI*0.3f, -Math::PI*0.1f, 0.0f, 0.8f); m_arm = TTA_FRIEND; @@ -390,7 +390,7 @@ CObject* CTaskTake::SearchFriendObject(float &angle, assert(pObj->Implements(ObjectInterfaceType::Powered)); - CObject* power = dynamic_cast(pObj)->GetPower(); + CObject* power = dynamic_cast(*pObj).GetPower(); if (power != nullptr) { if ( power->GetLock() ) continue; @@ -398,7 +398,7 @@ CObject* CTaskTake::SearchFriendObject(float &angle, } Math::Matrix* mat = pObj->GetWorldMatrix(0); - Math::Vector oPos = Math::Transform(*mat, dynamic_cast(pObj)->GetPowerPosition()); + Math::Vector oPos = Math::Transform(*mat, dynamic_cast(*pObj).GetPowerPosition()); float distance = fabs(Math::Distance(oPos, iPos) - (iRad+1.0f)); if ( distance <= dLimit ) @@ -406,7 +406,7 @@ CObject* CTaskTake::SearchFriendObject(float &angle, angle = Math::RotateAngle(oPos.x-iPos.x, iPos.z-oPos.z); // CW ! if ( Math::TestAngle(angle, iAngle-aLimit, iAngle+aLimit) ) { - Math::Vector powerPos = dynamic_cast(pObj)->GetPowerPosition(); + Math::Vector powerPos = dynamic_cast(*pObj).GetPowerPosition(); m_height = powerPos.y; return pObj; } @@ -430,8 +430,8 @@ bool CTaskTake::TransporterTakeObject() m_cargoType = cargo->GetType(); - dynamic_cast(cargo)->SetTransporter(m_object); - dynamic_cast(cargo)->SetTransporterPart(4); // takes with the hand + dynamic_cast(*cargo).SetTransporter(m_object); + dynamic_cast(*cargo).SetTransporterPart(4); // takes with the hand //? cargo->SetPosition(Math::Vector(2.2f, -1.0f, 1.1f)); cargo->SetPosition(Math::Vector(1.7f, -0.5f, 1.1f)); @@ -449,15 +449,15 @@ bool CTaskTake::TransporterTakeObject() if (other == nullptr) return false; assert(other->Implements(ObjectInterfaceType::Powered)); - CObject* cargo = dynamic_cast(other)->GetPower(); + CObject* cargo = dynamic_cast(*other).GetPower(); if (cargo == nullptr) return false; // the other does not have a battery? assert(cargo->Implements(ObjectInterfaceType::Transportable)); m_cargoType = cargo->GetType(); - dynamic_cast(other)->SetPower(nullptr); - dynamic_cast(cargo)->SetTransporter(m_object); - dynamic_cast(cargo)->SetTransporterPart(4); // takes with the hand + dynamic_cast(*other).SetPower(nullptr); + dynamic_cast(*cargo).SetTransporter(m_object); + dynamic_cast(*cargo).SetTransporterPart(4); // takes with the hand //? cargo->SetPosition(Math::Vector(2.2f, -1.0f, 1.1f)); cargo->SetPosition(Math::Vector(1.7f, -0.5f, 1.1f)); @@ -492,7 +492,7 @@ bool CTaskTake::TransporterDeposeObject() cargo->SetRotationZ(0.0f); cargo->FloorAdjust(); // plate well on the ground - dynamic_cast(cargo)->SetTransporter(nullptr); + dynamic_cast(*cargo).SetTransporter(nullptr); m_object->SetCargo(nullptr); // deposit } @@ -503,7 +503,7 @@ bool CTaskTake::TransporterDeposeObject() if (other == nullptr) return false; assert(other->Implements(ObjectInterfaceType::Powered)); - CObject* cargo = dynamic_cast(other)->GetPower(); + CObject* cargo = dynamic_cast(*other).GetPower(); if (cargo != nullptr) return false; // the other already has a battery? cargo = m_object->GetCargo(); @@ -511,14 +511,14 @@ bool CTaskTake::TransporterDeposeObject() assert(cargo->Implements(ObjectInterfaceType::Transportable)); m_cargoType = cargo->GetType(); - dynamic_cast(other)->SetPower(cargo); - dynamic_cast(cargo)->SetTransporter(other); + dynamic_cast(*other).SetPower(cargo); + dynamic_cast(*cargo).SetTransporter(other); - cargo->SetPosition(dynamic_cast(other)->GetPowerPosition()); + cargo->SetPosition(dynamic_cast(*other).GetPowerPosition()); cargo->SetRotationY(0.0f); cargo->SetRotationX(0.0f); cargo->SetRotationZ(0.0f); - dynamic_cast(cargo)->SetTransporterPart(0); // carried by the base + dynamic_cast(*cargo).SetTransporterPart(0); // carried by the base m_object->SetCargo(nullptr); // deposit } diff --git a/src/object/task/taskterraform.cpp b/src/object/task/taskterraform.cpp index c0267230..c0160bf8 100644 --- a/src/object/task/taskterraform.cpp +++ b/src/object/task/taskterraform.cpp @@ -215,7 +215,7 @@ Error CTaskTerraform::Start() power = m_object->GetPower(); if ( power == nullptr || !power->Implements(ObjectInterfaceType::PowerContainer) ) return ERR_TERRA_ENERGY; - energy = dynamic_cast(power)->GetEnergy(); + energy = dynamic_cast(*power).GetEnergy(); if ( energy < ENERGY_TERRA+0.05f ) return ERR_TERRA_ENERGY; speed = m_physics->GetMotorSpeed(); @@ -426,7 +426,7 @@ bool CTaskTerraform::Terraform() { if ( dist > 5.0f ) continue; m_engine->GetPyroManager()->Create(Gfx::PT_EXPLOT, pObj); - dynamic_cast(m_object)->DamageObject(DamageType::Explosive, 0.9f); + dynamic_cast(*m_object).DamageObject(DamageType::Explosive, 0.9f); } else if ( type == OBJECT_PLANT0 || type == OBJECT_PLANT1 || @@ -444,6 +444,7 @@ bool CTaskTerraform::Terraform() { if ( dist > 7.5f ) continue; m_engine->GetPyroManager()->Create(Gfx::PT_FRAGV, pObj); + } else // Other? { @@ -454,7 +455,7 @@ bool CTaskTerraform::Terraform() else { if ( !pObj->Implements(ObjectInterfaceType::Movable) ) continue; - motion = dynamic_cast(pObj)->GetMotion(); + motion = dynamic_cast(*pObj).GetMotion(); dist = Math::Distance(m_terraPos, pObj->GetPosition()); if ( dist > ACTION_RADIUS ) continue; @@ -462,13 +463,13 @@ bool CTaskTerraform::Terraform() if ( type == OBJECT_ANT || type == OBJECT_SPIDER ) { assert(pObj->Implements(ObjectInterfaceType::TaskExecutor)); - dynamic_cast(pObj)->StopForegroundTask(); + dynamic_cast(*pObj).StopForegroundTask(); int actionType = -1; if (type == OBJECT_ANT) actionType = MAS_BACK1; if (type == OBJECT_SPIDER) actionType = MSS_BACK1; motion->SetAction(actionType, 0.8f+Math::Rand()*0.3f); - dynamic_cast(pObj)->SetFixed(true); // not moving + dynamic_cast(*pObj).SetFixed(true); // not moving if ( dist > 5.0f ) continue; m_engine->GetPyroManager()->Create(Gfx::PT_EXPLOO, pObj); diff --git a/src/physics/physics.cpp b/src/physics/physics.cpp index 7dd79633..d3b69cd8 100644 --- a/src/physics/physics.cpp +++ b/src/physics/physics.cpp @@ -804,7 +804,7 @@ void CPhysics::MotorUpdate(float aTime, float rTime) if (m_object->Implements(ObjectInterfaceType::Powered)) { - power = dynamic_cast(dynamic_cast(m_object)->GetPower()); // searches for the object battery uses + power = dynamic_cast(dynamic_cast(*m_object).GetPower()); // searches for the object battery uses if ( GetObjectEnergy(m_object) == 0.0f ) // no battery or flat? { motorSpeed.x = 0.0f; @@ -822,7 +822,7 @@ void CPhysics::MotorUpdate(float aTime, float rTime) } } - if ( m_object->GetType() == OBJECT_HUMAN && dynamic_cast(m_object)->GetDying() == DeathType::Dead ) // dead man? + if ( m_object->GetType() == OBJECT_HUMAN && dynamic_cast(*m_object).GetDying() == DeathType::Dead ) // dead man? { motorSpeed.x = 0.0f; motorSpeed.z = 0.0f; @@ -852,7 +852,7 @@ void CPhysics::MotorUpdate(float aTime, float rTime) } if ( m_object->Implements(ObjectInterfaceType::JetFlying) && - dynamic_cast(m_object)->GetRange() > 0.0f ) // limited flight range? + dynamic_cast(*m_object).GetRange() > 0.0f ) // limited flight range? { CJetFlyingObject* jetFlying = dynamic_cast(m_object); if ( m_bLand || m_bSwim || m_bObstacle ) // on the ground or in the water? @@ -960,7 +960,7 @@ void CPhysics::MotorUpdate(float aTime, float rTime) bool reactorCool = true; if ( m_object->Implements(ObjectInterfaceType::JetFlying) ) { - reactorCool = dynamic_cast(m_object)->GetReactorRange() > 0.1f; + reactorCool = dynamic_cast(*m_object).GetReactorRange() > 0.1f; } if ( motorSpeed.y > 0.0f && reactorCool && pos.y < h ) { @@ -1463,7 +1463,7 @@ bool CPhysics::EventFrame(const Event &event) iAngle = angle = m_object->GetRotation(); // Accelerate is the descent, brake is the ascent. - if ( m_bFreeze || (m_object->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(m_object)->IsDying()) ) + if ( m_bFreeze || (m_object->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(*m_object).IsDying()) ) { m_linMotion.terrainSpeed.x = 0.0f; m_linMotion.terrainSpeed.z = 0.0f; @@ -1618,8 +1618,8 @@ void CPhysics::SoundMotor(float rTime) else if ( type == OBJECT_ANT ) { assert(m_object->Implements(ObjectInterfaceType::Destroyable)); - if ( dynamic_cast(m_object)->GetDying() == DeathType::Burning || - dynamic_cast(m_object)->GetFixed() ) + if ( dynamic_cast(*m_object).GetDying() == DeathType::Burning || + dynamic_cast(*m_object).GetFixed() ) { if ( m_lastSoundInsect <= 0.0f ) { @@ -1649,7 +1649,7 @@ void CPhysics::SoundMotor(float rTime) else m_lastSoundInsect = 1.5f+Math::Rand()*4.0f; } } - else if ( dynamic_cast(m_object)->GetDying() == DeathType::Burning ) + else if ( dynamic_cast(*m_object).GetDying() == DeathType::Burning ) { if ( m_lastSoundInsect <= 0.0f ) { @@ -1670,7 +1670,7 @@ void CPhysics::SoundMotor(float rTime) else m_lastSoundInsect = 1.5f+Math::Rand()*4.0f; } } - else if ( dynamic_cast(m_object)->GetDying() == DeathType::Burning ) + else if ( dynamic_cast(*m_object).GetDying() == DeathType::Burning ) { if ( m_lastSoundInsect <= 0.0f ) { @@ -1682,8 +1682,8 @@ void CPhysics::SoundMotor(float rTime) else if ( type == OBJECT_SPIDER ) { assert(m_object->Implements(ObjectInterfaceType::Destroyable)); - if ( dynamic_cast(m_object)->GetDying() == DeathType::Burning || - dynamic_cast(m_object)->GetFixed() ) + if ( dynamic_cast(*m_object).GetDying() == DeathType::Burning || + dynamic_cast(*m_object).GetFixed() ) { if ( m_lastSoundInsect <= 0.0f ) { @@ -2506,7 +2506,7 @@ int CPhysics::ObjectAdapt(const Math::Vector &pos, const Math::Vector &angle) int colType; ObjectType iType, oType; - if ( m_object->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(m_object)->IsDying() ) return 0; // is burning or exploding? + if ( m_object->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(*m_object).IsDying() ) return 0; // is burning or exploding? if ( !m_object->GetCollisions() ) return 0; // iiPos = sphere center is the old position. @@ -2525,7 +2525,7 @@ int CPhysics::ObjectAdapt(const Math::Vector &pos, const Math::Vector &angle) { if ( pObj == m_object ) continue; // yourself? if (IsObjectBeingTransported(pObj)) continue; - if ( pObj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(pObj)->GetDying() == DeathType::Exploding ) continue; // is exploding? + if ( pObj->Implements(ObjectInterfaceType::Destroyable) && dynamic_cast(*pObj).GetDying() == DeathType::Exploding ) continue; // is exploding? oType = pObj->GetType(); if ( oType == OBJECT_TOTO ) continue; @@ -2627,7 +2627,7 @@ int CPhysics::ObjectAdapt(const Math::Vector &pos, const Math::Vector &angle) CPhysics* ph = nullptr; if (pObj->Implements(ObjectInterfaceType::Movable)) - ph = dynamic_cast(pObj)->GetPhysics(); + ph = dynamic_cast(*pObj).GetPhysics(); if ( ph != nullptr ) { oAngle = pObj->GetRotation(); @@ -2727,7 +2727,7 @@ bool CPhysics::ExploOther(ObjectType iType, if ( force > destructionForce && destructionForce >= 0.0f ) { // TODO: implement "killer"? - dynamic_cast(pObj)->DamageObject(damageType); + dynamic_cast(*pObj).DamageObject(damageType); } } @@ -2753,7 +2753,7 @@ bool CPhysics::ExploOther(ObjectType iType, { assert(pObj->Implements(ObjectInterfaceType::Damageable)); // TODO: implement "killer"? - dynamic_cast(pObj)->DamageObject(DamageType::Collision, force/400.0f); + dynamic_cast(*pObj).DamageObject(DamageType::Collision, force/400.0f); } if (oType == OBJECT_MOBILEwa || @@ -2790,7 +2790,7 @@ bool CPhysics::ExploOther(ObjectType iType, { assert(pObj->Implements(ObjectInterfaceType::Damageable)); // TODO: implement "killer"? - dynamic_cast(pObj)->DamageObject(DamageType::Collision, force/200.0f); + dynamic_cast(*pObj).DamageObject(DamageType::Collision, force/200.0f); } } @@ -2830,7 +2830,7 @@ int CPhysics::ExploHimself(ObjectType iType, ObjectType oType, float force) if ( force > destructionForce && destructionForce >= 0.0f ) { // TODO: implement "killer"? - dynamic_cast(m_object)->DamageObject(DamageType::Explosive); + dynamic_cast(*m_object).DamageObject(DamageType::Explosive); return 2; } @@ -2917,7 +2917,7 @@ int CPhysics::ExploHimself(ObjectType iType, ObjectType oType, float force) } // TODO: implement "killer"? - if ( dynamic_cast(m_object)->DamageObject(DamageType::Collision, force) ) return 2; + if ( dynamic_cast(*m_object).DamageObject(DamageType::Collision, force) ) return 2; } } @@ -2969,9 +2969,9 @@ void CPhysics::PowerParticle(float factor, bool bBreak) bCarryPower = false; if (m_object->Implements(ObjectInterfaceType::Carrier)) { - CObject* cargo = dynamic_cast(m_object)->GetCargo(); + CObject* cargo = dynamic_cast(*m_object).GetCargo(); if ( cargo != nullptr && cargo->Implements(ObjectInterfaceType::PowerContainer) && - dynamic_cast(cargo)->IsRechargeable() && + dynamic_cast(*cargo).IsRechargeable() && m_object->GetPartRotationZ(1) == ARM_STOCK_ANGLE1 ) { bCarryPower = true; // carries a battery @@ -3264,7 +3264,7 @@ void CPhysics::MotorParticle(float aTime, float rTime) } else // in flight? { - if ( !m_bMotor || (m_object->Implements(ObjectInterfaceType::JetFlying) && dynamic_cast(m_object)->GetReactorRange() == 0.0f) ) return; + if ( !m_bMotor || (m_object->Implements(ObjectInterfaceType::JetFlying) && dynamic_cast(*m_object).GetReactorRange() == 0.0f) ) return; if ( m_reactorTemperature < 1.0f ) // not too hot? { @@ -3394,7 +3394,7 @@ void CPhysics::MotorParticle(float aTime, float rTime) } else // in flight? { - if ( !m_bMotor || (m_object->Implements(ObjectInterfaceType::JetFlying) && dynamic_cast(m_object)->GetReactorRange() == 0.0f) ) return; + if ( !m_bMotor || (m_object->Implements(ObjectInterfaceType::JetFlying) && dynamic_cast(*m_object).GetReactorRange() == 0.0f) ) return; if ( aTime-m_lastMotorParticle < m_engine->ParticleAdapt(0.02f) ) return; m_lastMotorParticle = aTime; @@ -3455,7 +3455,7 @@ void CPhysics::MotorParticle(float aTime, float rTime) if ( (type == OBJECT_HUMAN || type == OBJECT_TECH) && m_bSwim ) { - if ( !m_object->Implements(ObjectInterfaceType::Destroyable) || dynamic_cast(m_object)->GetDying() != DeathType::Dead ) + if ( !m_object->Implements(ObjectInterfaceType::Destroyable) || dynamic_cast(*m_object).GetDying() != DeathType::Dead ) { h = Math::Mod(aTime, 5.0f); if ( h < 3.5f && ( h < 1.5f || h > 1.6f ) ) return; @@ -3778,7 +3778,7 @@ Error CPhysics::GetError() if (m_object->Implements(ObjectInterfaceType::ProgramStorage)) { - if ( dynamic_cast(m_object)->GetActiveVirus() ) + if ( dynamic_cast(*m_object).GetActiveVirus() ) { return ERR_VEH_VIRUS; } @@ -3786,14 +3786,14 @@ Error CPhysics::GetError() if (m_object->Implements(ObjectInterfaceType::Powered)) { - CObject* power = dynamic_cast(m_object)->GetPower(); // searches for the object battery used + CObject* power = dynamic_cast(*m_object).GetPower(); // searches for the object battery used if (power == nullptr || !power->Implements(ObjectInterfaceType::PowerContainer)) { return ERR_VEH_POWER; } else { - if ( dynamic_cast(power)->GetEnergy() == 0.0f ) return ERR_VEH_ENERGY; + if ( dynamic_cast(*power).GetEnergy() == 0.0f ) return ERR_VEH_ENERGY; } } diff --git a/src/script/scriptfunc.cpp b/src/script/scriptfunc.cpp index b72ef470..ad1a3839 100644 --- a/src/script/scriptfunc.cpp +++ b/src/script/scriptfunc.cpp @@ -729,15 +729,18 @@ bool CScriptFunctions::rDelete(CBotVar* var, CBotVar* result, int& exception, vo } CObject* obj = CObjectManager::GetInstancePointer()->GetObjectById(rank); - if ( obj == nullptr || (obj->Implements(ObjectInterfaceType::Old) && dynamic_cast(obj)->IsDying()) ) + if ( obj == nullptr || (obj->Implements(ObjectInterfaceType::Old) && dynamic_cast(*obj).IsDying()) ) { return true; } else { + CScript* script = static_cast(user); + bool deleteSelf = (obj == script->m_object); + if ( exploType != DestructionType::NoEffect && obj->Implements(ObjectInterfaceType::Destroyable) ) { - dynamic_cast(obj)->DestroyObject(static_cast(exploType)); + dynamic_cast(*obj).DestroyObject(static_cast(exploType)); } else { @@ -753,12 +756,13 @@ bool CScriptFunctions::rDelete(CBotVar* var, CBotVar* result, int& exception, vo } CObjectManager::GetInstancePointer()->DeleteObject(obj); } + // Returning "false" here makes sure the program doesn't try to keep executing + // if the robot just destroyed itself using delete(this.id) + // See issue #925 + return !deleteSelf; } - // Returning "false" here makes sure the program doesn't try to keep executing if the robot just destroyed itself - // using delete(this.id) - // See issue #925 - return false; + return true; } static CBotTypResult compileSearch(CBotVar* &var, void* user, CBotTypResult returnValue) @@ -1653,7 +1657,7 @@ bool CScriptFunctions::rProduce(CBotVar* var, CBotVar* result, int& exception, v CObjectManager::GetInstancePointer()->CreateObject(pos, angle, OBJECT_EGG); if (object->Implements(ObjectInterfaceType::Programmable)) { - dynamic_cast(object)->SetActivity(false); + dynamic_cast(*object).SetActivity(false); } } else @@ -1662,7 +1666,11 @@ bool CScriptFunctions::rProduce(CBotVar* var, CBotVar* result, int& exception, v { power = 1.0f; } - object = CObjectManager::GetInstancePointer()->CreateObject(pos, angle, type, power); + bool exists = IsValidObjectTypeId(type) && type != OBJECT_NULL && type != OBJECT_MAX && type != OBJECT_MOBILEpr; + if (exists) + { + object = CObjectManager::GetInstancePointer()->CreateObject(pos, angle, type, power); + } if (object == nullptr) { result->SetValInt(1); // error @@ -1671,7 +1679,7 @@ bool CScriptFunctions::rProduce(CBotVar* var, CBotVar* result, int& exception, v if (type == OBJECT_MOBILEdr) { assert(object->Implements(ObjectInterfaceType::Old)); // TODO: temporary hack - dynamic_cast(object)->SetManual(true); + dynamic_cast(*object).SetManual(true); } script->m_main->CreateShortcuts(); } @@ -1686,7 +1694,7 @@ bool CScriptFunctions::rProduce(CBotVar* var, CBotVar* result, int& exception, v programStorage->ReadProgram(program, name2.c_str()); program->readOnly = true; program->filename = name; - dynamic_cast(object)->RunProgram(program); + dynamic_cast(*object).RunProgram(program); } } @@ -2292,7 +2300,7 @@ bool CScriptFunctions::rReceive(CBotVar* var, CBotVar* result, int& exception, v return true; } - CExchangePost* exchangePost = dynamic_cast(script->m_taskExecutor->GetForegroundTask())->FindExchangePost(power); + CExchangePost* exchangePost = dynamic_cast(*script->m_taskExecutor->GetForegroundTask()).FindExchangePost(power); script->m_returnValue = exchangePost->GetInfoValue(p); } if ( !WaitForForegroundTask(script, result, exception) ) return false; // not finished @@ -2577,7 +2585,7 @@ bool CScriptFunctions::rShield(CBotVar* var, CBotVar* result, int& exception, vo } else // up ? { - dynamic_cast(pThis)->SetShieldRadius(radius); + dynamic_cast(*pThis).SetShieldRadius(radius); err = script->m_taskExecutor->StartTaskShield(TSM_UP, 1000.0f); if ( err != ERR_OK ) { @@ -2595,7 +2603,7 @@ bool CScriptFunctions::rShield(CBotVar* var, CBotVar* result, int& exception, vo else // up? { //? result->SetValInt(1); // shows the error - dynamic_cast(pThis)->SetShieldRadius(radius); + dynamic_cast(*pThis).SetShieldRadius(radius); script->m_taskExecutor->StartTaskShield(TSM_UPDATE, 0.0f); } } @@ -2769,7 +2777,7 @@ bool CScriptFunctions::rMotor(CBotVar* var, CBotVar* result, int& exception, voi if ( turn < -1.0f ) turn = -1.0f; if ( turn > 1.0f ) turn = 1.0f; - if ( dynamic_cast(pThis) != nullptr && dynamic_cast(pThis)->GetFixed() ) // ant on the back? + if ( dynamic_cast(pThis) != nullptr && dynamic_cast(*pThis).GetFixed() ) // ant on the back? { speed = 0.0f; turn = 0.0f; @@ -2878,7 +2886,7 @@ bool CScriptFunctions::rCmdline(CBotVar* var, CBotVar* result, int& exception, v assert(pThis->Implements(ObjectInterfaceType::Programmable)); rank = var->GetValInt(); - value = dynamic_cast(pThis)->GetCmdLine(rank); + value = dynamic_cast(*pThis).GetCmdLine(rank); result->SetValFloat(value); return true; diff --git a/src/sound/oalsound/alsound.cpp b/src/sound/oalsound/alsound.cpp index f27a462b..26006198 100644 --- a/src/sound/oalsound/alsound.cpp +++ b/src/sound/oalsound/alsound.cpp @@ -47,22 +47,7 @@ void CALSound::CleanUp() if (m_enabled) { GetLogger()->Info("Unloading files and closing device...\n"); - StopAll(); - StopMusic(); - - m_channels.clear(); - - m_currentMusic.reset(); - - m_oldMusic.clear(); - - m_previousMusic.music.reset(); - - m_sounds.clear(); - - m_music.clear(); - - m_enabled = false; + Reset(); alcDestroyContext(m_context); alcCloseDevice(m_device); @@ -99,6 +84,24 @@ bool CALSound::Create() return true; } +void CALSound::Reset() +{ + StopAll(); + StopMusic(); + + m_channels.clear(); + + m_currentMusic.reset(); + + m_oldMusic.clear(); + + m_previousMusic.music.reset(); + + m_sounds.clear(); + + m_music.clear(); +} + bool CALSound::GetEnable() { return m_enabled; diff --git a/src/sound/oalsound/alsound.h b/src/sound/oalsound/alsound.h index 6f76c268..75e3d7f6 100644 --- a/src/sound/oalsound/alsound.h +++ b/src/sound/oalsound/alsound.h @@ -84,6 +84,7 @@ public: ~CALSound(); bool Create() override; + void Reset() override; bool Cache(SoundType, const std::string &) override; void CacheMusic(const std::string &) override; bool IsCached(SoundType) override; diff --git a/src/sound/sound.cpp b/src/sound/sound.cpp index 1cc23c4b..87d7266c 100644 --- a/src/sound/sound.cpp +++ b/src/sound/sound.cpp @@ -52,6 +52,10 @@ void CSoundInterface::CacheAll() } } +void CSoundInterface::Reset() +{ +} + bool CSoundInterface::Cache(SoundType sound, const std::string &file) { return true; diff --git a/src/sound/sound.h b/src/sound/sound.h index 78a5449d..314eb856 100644 --- a/src/sound/sound.h +++ b/src/sound/sound.h @@ -72,6 +72,10 @@ public: */ void CacheAll(); + /** Stop all sounds and music and clean cache. + */ + virtual void Reset(); + /** Function called to cache sound effect file. * This function is called by plugin interface for each file. * \param sound - id of a file, will be used to identify sound files diff --git a/src/ui/controls/edit.cpp b/src/ui/controls/edit.cpp index 62987308..570bd518 100644 --- a/src/ui/controls/edit.cpp +++ b/src/ui/controls/edit.cpp @@ -1299,7 +1299,9 @@ void CEdit::SetText(const std::string& text, bool bNew) if( m_len >= GetMaxChar() ) m_len = GetMaxChar(); m_text.resize( m_len + 1, '\0' ); + m_text[m_len] = '\0'; m_format.resize( m_len + 1, m_fontType ); + m_format[m_len] = m_fontType; font = m_fontType; j = 0; diff --git a/src/ui/controls/map.cpp b/src/ui/controls/map.cpp index ef485d82..9d9ebb48 100644 --- a/src/ui/controls/map.cpp +++ b/src/ui/controls/map.cpp @@ -1197,7 +1197,7 @@ void CMap::UpdateObject(CObject* pObj) type != OBJECT_WORM && type != OBJECT_MOBILEtg ) { - if (pObj->Implements(ObjectInterfaceType::Controllable) && !dynamic_cast(pObj)->GetSelectable()) return; + if (pObj->Implements(ObjectInterfaceType::Controllable) && !dynamic_cast(*pObj).GetSelectable()) return; } if ( pObj->GetProxyActivate() ) return; if (IsObjectBeingTransported(pObj)) return; @@ -1330,7 +1330,7 @@ void CMap::UpdateObject(CObject* pObj) color != MAPCOLOR_MOVE ) return; }*/ - if ( pObj->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(pObj)->GetSelect() ) + if ( pObj->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(*pObj).GetSelect() ) { m_map[MAPMAXOBJECT-1].type = type; m_map[MAPMAXOBJECT-1].object = pObj; diff --git a/src/ui/controls/target.cpp b/src/ui/controls/target.cpp index 2260aea1..24fa3c9a 100644 --- a/src/ui/controls/target.cpp +++ b/src/ui/controls/target.cpp @@ -143,13 +143,13 @@ CObject* CTarget::DetectFriendObject(Math::Point pos) CObject* target = obj; if ( obj->Implements(ObjectInterfaceType::PowerContainer) && IsObjectBeingTransported(obj) ) { - target = dynamic_cast(obj)->GetTransporter(); + target = dynamic_cast(*obj).GetTransporter(); } if ( !target->GetDetectable() ) continue; if ( target->GetProxyActivate() ) continue; - if ( target->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(target)->GetSelect() ) continue; - if ( !target->Implements(ObjectInterfaceType::Controllable) || !dynamic_cast(target)->GetSelectable() ) continue; + if ( target->Implements(ObjectInterfaceType::Controllable) && dynamic_cast(*target).GetSelect() ) continue; + if ( !target->Implements(ObjectInterfaceType::Controllable) || !dynamic_cast(*target).GetSelectable() ) continue; if (!target->Implements(ObjectInterfaceType::Old)) continue; // TODO: To be removed after COldObjectInterface is gone diff --git a/src/ui/displayinfo.cpp b/src/ui/displayinfo.cpp index 107e6b07..09f6d513 100644 --- a/src/ui/displayinfo.cpp +++ b/src/ui/displayinfo.cpp @@ -109,7 +109,7 @@ bool CDisplayInfo::EventProcess(const Event &event) if ( m_toto != nullptr ) { assert(m_toto->Implements(ObjectInterfaceType::Movable)); - CMotionToto* toto = static_cast(dynamic_cast(m_toto)->GetMotion()); + CMotionToto* toto = static_cast(dynamic_cast(*m_toto).GetMotion()); assert(toto != nullptr); toto->SetMousePos(event.mousePos); } @@ -449,7 +449,7 @@ void CDisplayInfo::StartDisplayInfo(std::string filename, int index, bool bSoluc m_toto->SetDrawFront(true); assert(m_toto->Implements(ObjectInterfaceType::Movable)); - CMotionToto* toto = static_cast(dynamic_cast(m_toto)->GetMotion()); + CMotionToto* toto = static_cast(dynamic_cast(*m_toto).GetMotion()); assert(toto != nullptr); toto->StartDisplayInfo(); } @@ -840,7 +840,7 @@ void CDisplayInfo::StopDisplayInfo() if ( m_toto != nullptr ) { assert(m_toto->Implements(ObjectInterfaceType::Movable)); - CMotionToto* toto = static_cast(dynamic_cast(m_toto)->GetMotion()); + CMotionToto* toto = static_cast(dynamic_cast(*m_toto).GetMotion()); assert(toto != nullptr); toto->StopDisplayInfo(); } diff --git a/src/ui/displaytext.cpp b/src/ui/displaytext.cpp index 58d085f5..b21263b4 100644 --- a/src/ui/displaytext.cpp +++ b/src/ui/displaytext.cpp @@ -258,7 +258,7 @@ void CDisplayText::DisplayText(const char *text, Math::Vector goal, float height if ( toto != nullptr ) { assert(toto->Implements(ObjectInterfaceType::Movable)); - motion = dynamic_cast(toto)->GetMotion(); + motion = dynamic_cast(*toto).GetMotion(); if ( type == TT_ERROR ) { diff --git a/src/ui/mainshort.cpp b/src/ui/mainshort.cpp index a8fbbe29..9e2a51a0 100644 --- a/src/ui/mainshort.cpp +++ b/src/ui/mainshort.cpp @@ -150,7 +150,7 @@ bool CMainShort::CreateShortcuts() for (CObject* pObj : CObjectManager::GetInstancePointer()->GetAllObjects()) { if ( !pObj->GetDetectable() ) continue; - if ( pObj->Implements(ObjectInterfaceType::Controllable) && !dynamic_cast(pObj)->GetSelectable() ) continue; + if ( pObj->Implements(ObjectInterfaceType::Controllable) && !dynamic_cast(*pObj).GetSelectable() ) continue; if ( pObj->GetProxyActivate() ) continue; int icon = GetShortcutIcon(pObj->GetType()); @@ -274,9 +274,9 @@ bool CMainShort::UpdateShortcuts() if ( pc != nullptr ) { assert(m_shortcuts[i]->Implements(ObjectInterfaceType::Controllable)); - pc->SetState(STATE_CHECK, dynamic_cast(m_shortcuts[i])->GetSelect()); - pc->SetState(STATE_RUN, m_shortcuts[i]->Implements(ObjectInterfaceType::Programmable) && dynamic_cast(m_shortcuts[i])->IsProgram()); - pc->SetState(STATE_DAMAGE, dynamic_cast(m_shortcuts[i])->IsDamaging()); + pc->SetState(STATE_CHECK, dynamic_cast(*m_shortcuts[i]).GetSelect()); + pc->SetState(STATE_RUN, m_shortcuts[i]->Implements(ObjectInterfaceType::Programmable) && dynamic_cast(*m_shortcuts[i]).IsProgram()); + pc->SetState(STATE_DAMAGE, dynamic_cast(*m_shortcuts[i]).IsDamaging()); } } return true; diff --git a/src/ui/mainui.cpp b/src/ui/mainui.cpp index a7cbab61..80fd5bc5 100644 --- a/src/ui/mainui.cpp +++ b/src/ui/mainui.cpp @@ -49,6 +49,7 @@ #include "ui/screen/screen_level_list.h" #include "ui/screen/screen_loading.h" #include "ui/screen/screen_main_menu.h" +#include "ui/screen/screen_mod_list.h" #include "ui/screen/screen_player_select.h" #include "ui/screen/screen_quit.h" #include "ui/screen/screen_setup_controls.h" @@ -80,6 +81,7 @@ CMainUserInterface::CMainUserInterface() m_screenIORead = MakeUnique(m_screenLevelList.get()); m_screenIOWrite = MakeUnique(m_screenLevelList.get()); m_screenLoading = MakeUnique(); + m_screenModList = MakeUnique(m_dialog.get(), m_app->GetModManager()); m_screenSetupControls = MakeUnique(); m_screenSetupDisplay = MakeUnique(); m_screenSetupGame = MakeUnique(); @@ -184,6 +186,10 @@ void CMainUserInterface::ChangePhase(Phase phase) m_screenLevelList->SetLevelCategory(m_main->GetLevelCategory()); m_currentScreen = m_screenLevelList.get(); } + if (m_phase == PHASE_MOD_LIST) + { + m_currentScreen = m_screenModList.get(); + } if (m_phase >= PHASE_SETUPd && m_phase <= PHASE_SETUPs) { CScreenSetup* screenSetup = GetSetupScreen(m_phase); @@ -531,6 +537,7 @@ void CMainUserInterface::FrameParticle(float rTime) } else if ( m_phase == PHASE_PLAYER_SELECT || m_phase == PHASE_LEVEL_LIST || + m_phase == PHASE_MOD_LIST || m_phase == PHASE_SETUPd || m_phase == PHASE_SETUPg || m_phase == PHASE_SETUPp || diff --git a/src/ui/mainui.h b/src/ui/mainui.h index c1f46e79..1a33331e 100644 --- a/src/ui/mainui.h +++ b/src/ui/mainui.h @@ -50,6 +50,7 @@ class CScreenIOWrite; class CScreenLevelList; class CScreenLoading; class CScreenMainMenu; +class CScreenModList; class CScreenPlayerSelect; class CScreenQuit; class CScreenSetup; @@ -117,6 +118,7 @@ protected: std::unique_ptr m_screenLevelList; std::unique_ptr m_screenLoading; std::unique_ptr m_screenMainMenu; + std::unique_ptr m_screenModList; std::unique_ptr m_screenPlayerSelect; std::unique_ptr m_screenQuit; std::unique_ptr m_screenSetupControls; diff --git a/src/ui/object_interface.cpp b/src/ui/object_interface.cpp index a24da9ba..9ca2c8b1 100644 --- a/src/ui/object_interface.cpp +++ b/src/ui/object_interface.cpp @@ -595,7 +595,7 @@ bool CObjectInterface::EventProcess(const Event &event) ps = static_cast< CSlider* >(pw->SearchControl(EVENT_OBJECT_DIMSHIELD)); if ( ps != nullptr ) { - dynamic_cast(m_object)->SetShieldRadius((ps->GetVisibleValue()-(RADIUS_SHIELD_MIN/g_unit))/((RADIUS_SHIELD_MAX-RADIUS_SHIELD_MIN)/g_unit)); + dynamic_cast(*m_object).SetShieldRadius((ps->GetVisibleValue()-(RADIUS_SHIELD_MIN/g_unit))/((RADIUS_SHIELD_MAX-RADIUS_SHIELD_MIN)/g_unit)); } } } @@ -1860,7 +1860,7 @@ void CObjectInterface::UpdateInterface() ps = static_cast< CSlider* >(pw->SearchControl(EVENT_OBJECT_DIMSHIELD)); if ( ps != nullptr ) { - ps->SetVisibleValue((RADIUS_SHIELD_MIN/g_unit)+dynamic_cast(m_object)->GetShieldRadius()*((RADIUS_SHIELD_MAX-RADIUS_SHIELD_MIN)/g_unit)); + ps->SetVisibleValue((RADIUS_SHIELD_MIN/g_unit)+dynamic_cast(*m_object).GetShieldRadius()*((RADIUS_SHIELD_MAX-RADIUS_SHIELD_MIN)/g_unit)); } } diff --git a/src/ui/screen/screen_main_menu.cpp b/src/ui/screen/screen_main_menu.cpp index ccabdfc1..c66e61a7 100644 --- a/src/ui/screen/screen_main_menu.cpp +++ b/src/ui/screen/screen_main_menu.cpp @@ -178,6 +178,13 @@ void CScreenMainMenu::CreateInterface() pb = pw->CreateButton(pos, ddim, 128+60, EVENT_INTERFACE_SATCOM); pb->SetState(STATE_SHADOW); + // Mods button + pos.x = 447.0f/640.0f; + pos.y = 313.0f/480.0f; + ddim.x = 0.09f; + pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_MODS); + pb->SetState(STATE_SHADOW); + SetBackground("textures/interface/interface.png"); CreateVersionDisplay(); } @@ -248,6 +255,9 @@ bool CScreenMainMenu::EventProcess(const Event &event) m_main->ChangePhase(PHASE_SATCOM); break; + case EVENT_INTERFACE_MODS: + m_main->ChangePhase(PHASE_MOD_LIST); + default: return true; } diff --git a/src/ui/screen/screen_mod_list.cpp b/src/ui/screen/screen_mod_list.cpp new file mode 100644 index 00000000..856f2a20 --- /dev/null +++ b/src/ui/screen/screen_mod_list.cpp @@ -0,0 +1,582 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#include "ui/screen/screen_mod_list.h" + +#include "common/config.h" + +#include "app/app.h" +#include "app/modman.h" + +#include "common/logger.h" +#include "common/restext.h" +#include "common/stringutils.h" + +#include "common/resources/resourcemanager.h" + +#include "common/system/system.h" + +#include "level/robotmain.h" + +#include "math/func.h" + +#include "sound/sound.h" + +#include "ui/controls/button.h" +#include "ui/controls/edit.h" +#include "ui/controls/interface.h" +#include "ui/controls/label.h" +#include "ui/controls/list.h" +#include "ui/controls/window.h" + +#include + +namespace Ui +{ + +CScreenModList::CScreenModList(CMainDialog* dialog, CModManager* modManager) + : m_dialog(dialog), + m_modManager(modManager) +{ +} + +void CScreenModList::CreateInterface() +{ + CWindow* pw; + CEdit* pe; + CLabel* pl; + CButton* pb; + CList* pli; + Math::Point pos, ddim; + std::string name; + + // Display the window + pos.x = 0.10f; + pos.y = 0.10f; + ddim.x = 0.80f; + ddim.y = 0.80f; + pw = m_interface->CreateWindows(pos, ddim, 12, EVENT_WINDOW5); + pw->SetClosable(true); + GetResource(RES_TEXT, RT_TITLE_MODS, name); + pw->SetName(name); + + pos.x = 0.10f; + pos.y = 0.40f; + ddim.x = 0.50f; + ddim.y = 0.50f; + pw->CreateGroup(pos, ddim, 5, EVENT_INTERFACE_GLINTl); // orange corner + pos.x = 0.40f; + pos.y = 0.10f; + ddim.x = 0.50f; + ddim.y = 0.50f; + pw->CreateGroup(pos, ddim, 4, EVENT_INTERFACE_GLINTr); // blue corner + + // Display the list of mods + pos.x = ox+sx*3; + pos.y = oy+sy*10.5f; + ddim.x = dim.x*7.5f; + ddim.y = dim.y*0.6f; + GetResource(RES_TEXT, RT_MOD_LIST, name); + pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL11, name); + pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT); + + pos.y = oy+sy*6.7f; + ddim.y = dim.y*4.6f; + ddim.x = dim.x*6.5f; + pli = pw->CreateList(pos, ddim, 0, EVENT_INTERFACE_MOD_LIST); + pli->SetState(STATE_SHADOW); + pli->SetState(STATE_EXTEND); + + // Displays the mod details + pos.x = ox+sx*9.5f; + pos.y = oy+sy*10.5f; + ddim.x = dim.x*7.5f; + ddim.y = dim.y*0.6f; + GetResource(RES_TEXT, RT_MOD_DETAILS, name); + pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL12, name); + pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT); + + pos.y = oy+sy*6.7f; + ddim.y = dim.y*4.3f; + ddim.x = dim.x*6.5f; + pe = pw->CreateEdit(pos, ddim, 0, EVENT_INTERFACE_MOD_DETAILS); + pe->SetState(STATE_SHADOW); + pe->SetMaxChar(500); + pe->SetEditCap(false); // just to see + pe->SetHighlightCap(true); + + pos = pli->GetPos(); + ddim = pli->GetDim(); + + // Displays the mod summary + pos.x = ox+sx*3; + pos.y = oy+sy*5.4f; + ddim.x = dim.x*6.5f; + ddim.y = dim.y*0.6f; + GetResource(RES_TEXT, RT_MOD_SUMMARY, name); + pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL13, name); + pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT); + + pos.x = ox+sx*3; + pos.y = oy+sy*3.6f; + ddim.x = dim.x*13.4f; + ddim.y = dim.y*1.9f; + pe = pw->CreateEdit(pos, ddim, 0, EVENT_INTERFACE_MOD_SUMMARY); + pe->SetState(STATE_SHADOW); + pe->SetMaxChar(500); + pe->SetEditCap(false); // just to see + pe->SetHighlightCap(true); + + // Apply button + pos.x = ox+sx*13.75f; + pos.y = oy+sy*2; + ddim.x = dim.x*2.0f; + ddim.y = dim.y*1; + pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_MODS_APPLY); + pb->SetState(STATE_SHADOW); + + // Display the enable/disable button + pos.x -= dim.x*2.3f; + ddim.x = dim.x*2.0f; + pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE); + pb->SetState(STATE_SHADOW); + + // Display the move up button + pos.x -= dim.x*0.8f; + pos.y = oy+sy*2.48; + ddim.x = dim.x*0.5; + ddim.y = dim.y*0.5; + pb = pw->CreateButton(pos, ddim, 49, EVENT_INTERFACE_MOD_MOVE_UP); + pb->SetState(STATE_SHADOW); + + // Display the move down button + pos.y = oy+sy*2; + pb = pw->CreateButton(pos, ddim, 50, EVENT_INTERFACE_MOD_MOVE_DOWN); + pb->SetState(STATE_SHADOW); + + // Display the refresh button + pos.x -= dim.x*1.3f; + pos.y = oy+sy*2; + ddim.x = dim.x*1; + ddim.y = dim.y*1; + pb = pw->CreateButton(pos, ddim, 87, EVENT_INTERFACE_MODS_REFRESH); + pb->SetState(STATE_SHADOW); + + // Display the open website button + pos.x -= dim.x*1.3f; + pb = pw->CreateButton(pos, ddim, 40, EVENT_INTERFACE_WORKSHOP); + pb->SetState(STATE_SHADOW); + + // Display the open directory button + pos.x -= dim.x*1.3f; + pb = pw->CreateButton(pos, ddim, 57, EVENT_INTERFACE_MODS_DIR); + pb->SetState(STATE_SHADOW); + + // Back button + pos.x = ox+sx*3; + ddim.x = dim.x*4; + pb = pw->CreateButton(pos, ddim, -1, EVENT_INTERFACE_BACK); + pb->SetState(STATE_SHADOW); + + FindMods(); + UpdateAll(); + + // Background + SetBackground("textures/interface/interface.png"); + CreateVersionDisplay(); +} + +bool CScreenModList::EventProcess(const Event &event) +{ + CWindow* pw; + CList* pl; + + const std::string workshopUrl = "https://www.moddb.com/games/colobot-gold-edition"; + const std::string modDir = CResourceManager::GetSaveLocation() + "/mods"; + + auto systemUtils = CSystemUtils::Create(); // platform-specific utils + + Mod const * mod; + + pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return false; + + if (event.type == pw->GetEventTypeClose() || + event.type == EVENT_INTERFACE_BACK || + (event.type == EVENT_KEY_DOWN && event.GetData()->key == KEY(ESCAPE))) + { + if (m_modManager->Changes()) + { + m_dialog->StartQuestion(RT_DIALOG_CHANGES_QUESTION, true, true, false, + [this]() + { + ApplyChanges(); + CloseWindow(); + }, + [this]() + { + CloseWindow(); + }); + } + else + { + CloseWindow(); + } + return false; + } + + switch( event.type ) + { + case EVENT_INTERFACE_MOD_LIST: + pl = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_LIST)); + if (pl == nullptr) break; + m_modSelectedIndex = pl->GetSelect(); + UpdateModSummary(); + UpdateModDetails(); + UpdateEnableDisableButton(); + UpdateUpDownButtons(); + break; + + case EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE: + mod = &m_modManager->GetMod(m_modSelectedIndex); + if (mod->enabled) + { + m_modManager->DisableMod(m_modSelectedIndex); + } + else + { + m_modManager->EnableMod(m_modSelectedIndex); + } + UpdateModList(); + UpdateEnableDisableButton(); + UpdateApplyButton(); + break; + + case EVENT_INTERFACE_MOD_MOVE_UP: + m_modSelectedIndex = m_modManager->MoveUp(m_modSelectedIndex); + UpdateModList(); + UpdateUpDownButtons(); + UpdateApplyButton(); + break; + + case EVENT_INTERFACE_MOD_MOVE_DOWN: + m_modSelectedIndex = m_modManager->MoveDown(m_modSelectedIndex); + UpdateModList(); + UpdateUpDownButtons(); + UpdateApplyButton(); + break; + + case EVENT_INTERFACE_MODS_REFRESH: + // Apply any changes before refresh so that the config file + // is better synchronized with the state of the game + case EVENT_INTERFACE_MODS_APPLY: + ApplyChanges(); + UpdateAll(); + // Start playing the main menu music again + if (!m_app->GetSound()->IsPlayingMusic()) + { + m_app->GetSound()->PlayMusic("music/Intro1.ogg", false); + m_app->GetSound()->CacheMusic("music/Intro2.ogg"); + } + break; + + case EVENT_INTERFACE_MODS_DIR: + if (!systemUtils->OpenPath(modDir)) + { + std::string title, text; + GetResource(RES_TEXT, RT_DIALOG_OPEN_PATH_FAILED_TITLE, title); + GetResource(RES_TEXT, RT_DIALOG_OPEN_PATH_FAILED_TEXT, text); + + // Workaround for Windows: the label skips everything after the first \\ character + std::string modDirWithoutBackSlashes = modDir; + std::replace(modDirWithoutBackSlashes.begin(), modDirWithoutBackSlashes.end(), '\\', '/'); + + m_dialog->StartInformation(title, title, StrUtils::Format(text.c_str(), modDirWithoutBackSlashes.c_str())); + } + break; + + case EVENT_INTERFACE_WORKSHOP: + if (!systemUtils->OpenWebsite(workshopUrl)) + { + std::string title, text; + GetResource(RES_TEXT, RT_DIALOG_OPEN_WEBSITE_FAILED_TITLE, title); + GetResource(RES_TEXT, RT_DIALOG_OPEN_WEBSITE_FAILED_TEXT, text); + m_dialog->StartInformation(title, title, StrUtils::Format(text.c_str(), workshopUrl.c_str())); + } + break; + + default: + return true; + } + return false; +} + +void CScreenModList::FindMods() +{ + m_modManager->FindMods(); + if (m_modManager->CountMods() != 0) + { + m_modSelectedIndex = Math::Clamp(m_modSelectedIndex, static_cast(0), m_modManager->CountMods() - 1); + } +} + +void CScreenModList::ApplyChanges() +{ + m_modManager->SaveMods(); + m_modManager->ReloadMods(); +} + +void CScreenModList::CloseWindow() +{ + m_main->ChangePhase(PHASE_MAIN_MENU); +} + +void CScreenModList::UpdateAll() +{ + UpdateModList(); + UpdateModDetails(); + UpdateModSummary(); + UpdateEnableDisableButton(); + UpdateApplyButton(); + UpdateUpDownButtons(); +} + +void CScreenModList::UpdateModList() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CList* pl = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_LIST)); + if (pl == nullptr) return; + + pl->Flush(); + + if (m_modManager->CountMods() == 0) + { + return; + } + + const auto& mods = m_modManager->GetMods(); + for (size_t i = 0; i < mods.size(); ++i) + { + const auto& mod = mods[i]; + const auto& name = mod.data.displayName; + pl->SetItemName(i, name); + pl->SetCheck(i, mod.enabled); + pl->SetEnable(i, true); + } + + pl->SetSelect(m_modSelectedIndex); + pl->ShowSelect(false); +} + +void CScreenModList::UpdateModDetails() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CEdit* pe = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_DETAILS)); + if (pe == nullptr) return; + + if (m_modManager->CountMods() == 0) + { + pe->SetText("No information"); + return; + } + + std::string details{}; + + const auto& mod = m_modManager->GetMod(m_modSelectedIndex); + const auto data = mod.data; + + details += "\\b;" + data.displayName + '\n'; + + std::string authorFieldName; + GetResource(RES_TEXT, RT_MOD_AUTHOR_FIELD_NAME, authorFieldName); + details += "\\s;" + authorFieldName + " "; + if (!data.author.empty()) + { + details += data.author; + } + else + { + std::string unknownAuthor; + GetResource(RES_TEXT, RT_MOD_UNKNOWN_AUTHOR, unknownAuthor); + details += unknownAuthor; + } + details += '\n'; + + details += '\n'; + + if (!data.version.empty()) + { + std::string versionFieldName; + GetResource(RES_TEXT, RT_MOD_VERSION_FIELD_NAME, versionFieldName); + details += "\\t;" + versionFieldName + '\n' + data.version + '\n'; + } + + if (!data.website.empty()) + { + std::string websiteFieldName; + GetResource(RES_TEXT, RT_MOD_WEBSITE_FIELD_NAME, websiteFieldName); + details += "\\t;" + websiteFieldName + '\n' + data.website + '\n'; + } + + std::string changesFieldName; + GetResource(RES_TEXT, RT_MOD_CHANGES_FIELD_NAME, changesFieldName); + details += "\\t;" + changesFieldName + '\n'; + if (!data.changes.empty()) + { + for (const auto& change : data.changes) + { + details += change + '\n'; + } + } + else + { + std::string noChanges; + GetResource(RES_TEXT, RT_MOD_NO_CHANGES, noChanges); + details += noChanges; + } + + pe->SetText(details); + + pe->SetFirstLine(0); +} + +void CScreenModList::UpdateModSummary() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CEdit* pe = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_SUMMARY)); + if (pe == nullptr) return; + + std::string noSummary; + GetResource(RES_TEXT, RT_MOD_NO_SUMMARY, noSummary); + + if (m_modManager->CountMods() == 0) + { + pe->SetText(noSummary); + return; + } + + const auto& mod = m_modManager->GetMod(m_modSelectedIndex); + + if (!mod.data.summary.empty()) + { + pe->SetText(mod.data.summary); + } + else + { + pe->SetText(noSummary); + } +} + +void CScreenModList::UpdateEnableDisableButton() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CButton* pb = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_ENABLE_OR_DISABLE)); + if (pb == nullptr) return; + + std::string buttonName{}; + + if (m_modManager->CountMods() == 0) + { + pb->ClearState(STATE_ENABLE); + + // Set some default name + GetResource(RES_TEXT, RT_MOD_ENABLE, buttonName); + pb->SetName(buttonName); + + return; + } + + const auto& mod = m_modManager->GetMod(m_modSelectedIndex); + + if (mod.enabled) + { + GetResource(RES_TEXT, RT_MOD_DISABLE, buttonName); + pb->SetName(buttonName); + } + else + { + GetResource(RES_TEXT, RT_MOD_ENABLE, buttonName); + pb->SetName(buttonName); + } +} + +void CScreenModList::UpdateApplyButton() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CButton* pb = static_cast(pw->SearchControl(EVENT_INTERFACE_MODS_APPLY)); + if (pb == nullptr) return; + + if (m_modManager->Changes()) + { + pb->SetState(STATE_ENABLE); + } + else + { + pb->ClearState(STATE_ENABLE); + } +} + +void CScreenModList::UpdateUpDownButtons() +{ + CWindow* pw = static_cast(m_interface->SearchControl(EVENT_WINDOW5)); + if (pw == nullptr) return; + + CButton* pb_up = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_MOVE_UP)); + if (pb_up == nullptr) return; + + CButton* pb_down = static_cast(pw->SearchControl(EVENT_INTERFACE_MOD_MOVE_DOWN)); + if (pb_down == nullptr) return; + + if (m_modManager->CountMods() == 0) + { + pb_up->ClearState(STATE_ENABLE); + pb_down->ClearState(STATE_ENABLE); + return; + } + + if (m_modSelectedIndex == 0) + { + pb_up->ClearState(STATE_ENABLE); + } + else + { + pb_up->SetState(STATE_ENABLE); + } + + if (m_modSelectedIndex >= m_modManager->CountMods() - 1) + { + pb_down->ClearState(STATE_ENABLE); + } + else + { + pb_down->SetState(STATE_ENABLE); + } +} + +} // namespace Ui diff --git a/src/ui/screen/screen_mod_list.h b/src/ui/screen/screen_mod_list.h new file mode 100644 index 00000000..ca2c130b --- /dev/null +++ b/src/ui/screen/screen_mod_list.h @@ -0,0 +1,66 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#pragma once + +#include "app/modman.h" + +#include "ui/maindialog.h" + +#include "ui/screen/screen.h" + +#include + +namespace Ui +{ + +/** + * \class CScreenModList + * \brief This class is the front-end for the \ref CModManager. + */ +class CScreenModList : public CScreen +{ +public: + CScreenModList(CMainDialog* dialog, CModManager* modManager); + + void CreateInterface() override; + bool EventProcess(const Event &event) override; + +protected: + void FindMods(); + void ApplyChanges(); + void CloseWindow(); + + void UpdateAll(); + void UpdateModList(); + void UpdateModDetails(); + void UpdateModSummary(); + void UpdateEnableDisableButton(); + void UpdateApplyButton(); + void UpdateUpDownButtons(); + +protected: + Ui::CMainDialog* m_dialog; + + CModManager* m_modManager; + + size_t m_modSelectedIndex = 0; +}; + +} // namespace Ui diff --git a/src/ui/screen/screen_setup_display.cpp b/src/ui/screen/screen_setup_display.cpp index 219c7f29..f465a488 100644 --- a/src/ui/screen/screen_setup_display.cpp +++ b/src/ui/screen/screen_setup_display.cpp @@ -293,6 +293,7 @@ void CScreenSetupDisplay::UpdateApply() CWindow* pw; CButton* pb; CList* pl; + CList* pvl; CCheck* pc; int sel2; bool bFull; @@ -309,6 +310,22 @@ void CScreenSetupDisplay::UpdateApply() pc = static_cast(pw->SearchControl(EVENT_INTERFACE_FULL)); bFull = pc->TestState(STATE_CHECK); + pvl = static_cast(pw->SearchControl(EVENT_INTERFACE_VSYNC)); + if (pvl == nullptr) return; + + switch (m_engine->GetVSync()) + { + case -1: //Adaptive? + pvl->SetSelect(1); + break; + case 0: //Off? + pvl->SetSelect(0); + break; + case 1: //On? + pvl->SetSelect(2); + break; + } + if ( sel2 == m_setupSelMode && bFull == m_setupFull ) { diff --git a/test/unit/common/colobot.ini b/test/unit/common/colobot.ini index c4d21623..7cc649b5 100644 --- a/test/unit/common/colobot.ini +++ b/test/unit/common/colobot.ini @@ -6,3 +6,8 @@ string_value=Hello world [test_int] int_value=42 + +[test_array] +string_array=AAA,Hello world,Gold Edition +int_array=2,3,1 +bool_array=1,0,1 diff --git a/test/unit/common/config_file_test.cpp b/test/unit/common/config_file_test.cpp index fcd8f036..762c3307 100644 --- a/test/unit/common/config_file_test.cpp +++ b/test/unit/common/config_file_test.cpp @@ -52,3 +52,25 @@ TEST_F(CConfigFileTest, ReadTest) ASSERT_TRUE(m_configFile.GetFloatProperty("test_float", "float_value", float_value)); ASSERT_FLOAT_EQ(1.5, float_value); } + +TEST_F(CConfigFileTest, ReadArrayTest) +{ + m_configFile.SetUseCurrentDirectory(true); + + ASSERT_TRUE(m_configFile.Init()); // load colobot.ini file + + std::vector expected_string_values = { "AAA", "Hello world", "Gold Edition" }; + std::vector string_values; + ASSERT_TRUE(m_configFile.GetArrayProperty("test_array", "string_array", string_values)); + ASSERT_EQ(expected_string_values, string_values); + + std::vector expected_int_values = { 2, 3, 1 }; + std::vector int_values; + ASSERT_TRUE(m_configFile.GetArrayProperty("test_array", "int_array", int_values)); + ASSERT_EQ(expected_int_values, int_values); + + std::vector expected_bool_values = { true, false, true }; + std::vector bool_values; + ASSERT_TRUE(m_configFile.GetArrayProperty("test_array", "bool_array", bool_values)); + ASSERT_EQ(expected_bool_values, bool_values); +} diff --git a/tools/blender-scripts.py b/tools/blender-scripts.py index d2273d67..b4104793 100644 --- a/tools/blender-scripts.py +++ b/tools/blender-scripts.py @@ -8,7 +8,7 @@ bl_info = { "name": "Colobot Model Format (.txt)", "author": "TerranovaTeam", - "version": (0, 0, 2), + "version": (0, 0, 3), "blender": (2, 6, 4), "location": "File > Export > Colobot (.txt)", "description": "Export Colobot Model Format (.txt)", @@ -35,7 +35,7 @@ FUZZY_TOLERANCE = 1e-5 class ColobotError(Exception): """Exception in I/O operations""" - def __init__(self, value): + def __init__(self, value, errcode=None): self.value = value def __str__(self): return repr(self.value) @@ -199,7 +199,8 @@ def write_colobot_model(filename, model): file.write('tex1 ' + t.mat.tex1 + '\n') file.write('tex2 ' + t.mat.tex2 + '\n') file.write('var_tex2 ' + ( 'Y' if t.mat.var_tex2 else 'N' + '\n' ) ) - file.write('lod_level ' + str(t.lod_level) + '\n') + if model.version == 1: + file.write('lod_level ' + str(t.lod_level) + '\n') file.write('state ' + str(t.mat.state) + '\n') file.write('\n') @@ -281,8 +282,8 @@ def read_colobot_model(filename): if (tokens[0] != 'version'): raise ColobotError("Invalid header", "version") model.version = int(tokens[1]) - if (model.version != 1): - raise ColobotError("Unknown model file version") + if (model.version != 1 and model.version != 2): + raise ColobotError("Unknown model file version "+str(model.version)) tokens, index = token_next_line(lines, index) if (tokens[0] != 'total_triangles'): @@ -329,10 +330,13 @@ def read_colobot_model(filename): raise ColobotError("Invalid triangle", "var_tex2") t.mat.var_tex2 = tokens[1] == 'Y' - tokens, index = token_next_line(lines, index) - if (tokens[0] != 'lod_level'): - raise ColobotError("Invalid triangle", "lod_level") - t.lod_level = int(tokens[1]) + if (model.version == 1): + tokens, index = token_next_line(lines, index) + if (tokens[0] != 'lod_level'): + raise ColobotError("Invalid triangle", "lod_level") + t.lod_level = int(tokens[1]) + else: + t.lod_level = 0 # constant tokens, index = token_next_line(lines, index) if (tokens[0] != 'state'): @@ -384,9 +388,9 @@ def append_obj_to_colobot_model(obj, model, scene, defaults): t.mat.specular[3] = mat.specular_alpha if (mat.texture_slots[0] != None): - t.tex1 = bpy.path.basename(mat.texture_slots[0].texture.image.filepath) + t.mat.tex1 = bpy.path.basename(mat.texture_slots[0].texture.image.filepath) if (mat.texture_slots[1] != None): - t.tex2 = bpy.path.basename(mat.texture_slots[1].texture.image.filepath) + t.mat.tex2 = bpy.path.basename(mat.texture_slots[1].texture.image.filepath) t.var_tex2 = mat.get('var_tex2', defaults['var_tex2']) t.state = mat.get('state', defaults['state']) @@ -589,7 +593,7 @@ class ExportColobotDialog(bpy.types.Operator): write_colobot_model(EXPORT_FILEPATH, model) except ColobotError as e: - self.report({'ERROR'}, e.args.join(": ")) + self.report({'ERROR'}, ": ".join(e.args)) return {'FINISHED'} self.report({'INFO'}, 'Export OK') @@ -665,7 +669,7 @@ class ImportColobotDialog(bpy.types.Operator): obj.layers = layers except ColobotError as e: - self.report({'ERROR'}, e.args.join(": ")) + self.report({'ERROR'}, ": ".join(e.args)) return {'FINISHED'} self.report({'INFO'}, 'Import OK')