diff --git a/.github/workflows/buildall.yml b/.github/workflows/buildall.yml new file mode 100644 index 00000000..41f05398 --- /dev/null +++ b/.github/workflows/buildall.yml @@ -0,0 +1,128 @@ +name: GLSMAC autobuild + +on: + push: + branches: [ "stable" ] + pull_request: + branches: [ "stable" ] + +jobs: + + prepare: + runs-on: ubuntu-latest + outputs: + upload_url: ${{ steps.create_release.outputs.upload_url }} + release_id: ${{ steps.create_release.outputs.id }} + ver: ${{ steps.info.outputs.ver }} + sha: ${{ steps.info.outputs.sha }} + steps: + - uses: benjlevesque/short-sha@v2.1 + id: short-sha + with: + length: 7 + - uses: dev-drprasad/delete-older-releases@v0.2.0 + with: + keep_latest: 4 + delete_tags: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: info + id: info + env: + ver: v0.3 + sha: ${{ steps.short-sha.outputs.sha }} + run: | + echo ${{ env.ver }} ${{ env.sha }} + echo "ver=${{ env.ver }}" >> $GITHUB_OUTPUT + echo "sha=${{ env.sha }}" >> $GITHUB_OUTPUT + - name: create-release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.info.outputs.ver }}-${{ steps.info.outputs.sha }} + release_name: ${{ steps.info.outputs.ver }}-${{ steps.info.outputs.sha }} + draft: false + prerelease: false + + + build_linux: + needs: [prepare] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: install_dependencies + run: | + sudo apt-get update + sudo apt-get install libfreetype-dev libsdl2-dev libsdl2-image-dev libglu-dev libglew-dev libossp-uuid-dev libyaml-cpp-dev + - name: prepare + run: | + mkdir build + - name: cmake + working-directory: ./build + run: cmake -DCMAKE_BUILD_TYPE=Portable64 .. + - name: make + working-directory: ./build + run: make + - name: data + working-directory: ./build + run: mv ../GLSMAC_data bin/ + - name: pack + working-directory: ./build + run: tar -C bin -zcvf GLSMAC.tar.gz GLSMAC GLSMAC_data + - name: publish + uses: actions/upload-artifact@v3 + with: + name: GLSMAC-linux64-bin + path: | + ./build/GLSMAC.tar.gz + - name: upload + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.prepare.outputs.upload_url }} + asset_path: ./build/GLSMAC.tar.gz + asset_name: GLSMAC-${{ needs.prepare.outputs.ver }}-linux64-${{ needs.prepare.outputs.sha }}.tar.gz + asset_content_type: application/gzip + + + build_windows: + needs: [prepare] + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: install_dependencies + run: | + choco install ninja + - name: prepare + run: | + mkdir build + - name: cmake + working-directory: ./build + run: cmake -G Ninja -DCMAKE_BUILD_TYPE=Portable64 -DVENDORED_DEPENDENCIES=1 .. + - name: make + working-directory: ./build + run: ninja + - name: data + working-directory: ./build + run: mv ../GLSMAC_data bin/ + - name: pack + working-directory: ./build/bin + run: Compress-Archive -Path GLSMAC.exe,GLSMAC_data -Destination ../GLSMAC.zip + - name: publish + uses: actions/upload-artifact@v3 + with: + name: GLSMAC-windows64-bin + path: | + ./build/GLSMAC.zip + - name: upload + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.prepare.outputs.upload_url }} + asset_path: ./build/GLSMAC.zip + asset_name: GLSMAC-${{ needs.prepare.outputs.ver }}-win64-${{ needs.prepare.outputs.sha }}.zip + asset_content_type: application/gzip diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml deleted file mode 100644 index 930a95b9..00000000 --- a/.github/workflows/c-cpp.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: C/C++ CI - -on: - push: - branches: [ "stable" ] - pull_request: - branches: [ "stable" ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - uses: benjlevesque/short-sha@v2.1 - id: short-sha - with: - length: 7 - - run: echo $SHA - env: - SHA: ${{ steps.short-sha.outputs.sha }} - - name: install_dependencies - run: | - sudo apt-get update - sudo apt-get install libfreetype-dev libsdl2-dev libsdl2-image-dev libglu-dev libglew-dev libossp-uuid-dev libyaml-cpp-dev - - name: cmake64 - run: cmake -DCMAKE_BUILD_TYPE=Portable64 . - - name: make64 - run: make - - name: data64 - run: mv GLSMAC_data bin/ - - name: pack64 - run: tar -C bin -zcvf GLSMAC-v0.3-${{ env.SHA }}-bin64.tar.gz GLSMAC GLSMAC_data - - name: publish - uses: actions/upload-artifact@v3 - with: - name: GLSMAC-ubuntu-bin - path: | - ./GLSMAC-v0.3-${{ env.SHA }}-bin64.tar.gz - - uses: dev-drprasad/delete-older-releases@v0.2.0 - with: - keep_latest: 4 - delete_tags: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: v0.3-${{ env.SHA }} - release_name: v0.3-${{ env.SHA }} - draft: false - prerelease: false - - name: Upload Release Asset 64 - id: upload-release-asset-64 - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps - name: upload - asset_path: ./GLSMAC-v0.3-${{ env.SHA }}-bin64.tar.gz - asset_name: GLSMAC-v0.3-${{ env.SHA }}-bin64.tar.gz - asset_content_type: application/gzip diff --git a/CMakeLists.txt b/CMakeLists.txt index 64035fef..cbb435f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,11 @@ INCLUDE_DIRECTORIES( "src" ) IF ( VISUAL_STUDIO ) # (Provided in CMakePresets) ADD_COMPILE_DEFINITIONS( _ITERATOR_DEBUG_LEVEL=0 ) ADD_COMPILE_DEFINITIONS( VISUAL_STUDIO ) +ELSEIF ( WIN32 ) # probably mingw or github runner + TARGET_LINK_LIBRARIES( ${PROJECT_NAME} PRIVATE wsock32 ) + TARGET_LINK_LIBRARIES( ${PROJECT_NAME} PRIVATE ws2_32 ) +# TARGET_LINK_OPTIONS( ${PROJECT_NAME} PRIVATE -static-libgcc -static-libstdc++ -static-winpthread ) + TARGET_LINK_OPTIONS( ${PROJECT_NAME} PRIVATE -static ) ENDIF () # needed for some uncommon IDE and OS combinations diff --git a/GLSMAC_data/default/factions.gls.js b/GLSMAC_data/default/factions.gls.js index 02fde6c4..425d3c04 100644 --- a/GLSMAC_data/default/factions.gls.js +++ b/GLSMAC_data/default/factions.gls.js @@ -1,40 +1,24 @@ -const faction = (id, name, text_color, border_color, pcx, extra) => { - return [id, { - name: name, - colors: { - text: text_color, - border: border_color, - }, - bases: { - render: { - type: 'sprite_grid', - file: pcx, - grid_x: 1, grid_y: 1, - cell_width: 100, cell_height: 75, - //cell_cx: 50, cell_cy: 38, - cell_padding: 1, - /*scale_x: 0.7, - scale_y: 0.7,*/ - } - }, - } + extra]; -}; +return { -const factions = [ - faction('GAIANS', 'Gaians', #to_color(16, 228, 0), #to_color(0, 252, 0), 'gaians.pcx', {}), - faction('HIVE', 'Hive', #to_color(0, 97, 255), #to_color(0, 0, 255), 'hive.pcx', {}), - faction('UNIVERSITY', 'University', #to_color(216, 224, 244), #to_color(255, 255, 255), 'univ.pcx', {}), - faction('MORGANITES', 'Morganites', #to_color(255, 255, 0), #to_color(255, 255, 0), 'morgan.pcx', {}), - faction('SPARTANS', 'Spartans', #to_color(136, 166, 166), #to_color(0, 0, 0), 'spartans.pcx', {}), - faction('BELIEVERS', 'Believers', #to_color(224, 156, 28), #to_color(236, 92, 0), 'believe.pcx', {}), - faction('PEACEKEEPERS', 'Peacekeepers', #to_color(164, 176, 232), #to_color(164, 176, 232), 'peace.pcx', {}), - faction('CONSCIOUSNESS', 'Consciousness', #to_color(44, 128, 104), #to_color(44, 128, 104), 'cyborg.pcx', {}), - faction('PIRATES', 'Pirates', #to_color(0, 255, 255), #to_color(0, 255, 255), 'pirates.pcx', {}), - faction('DRONES', 'Drones', #to_color(173, 196, 192), #to_color(136, 12, 12), 'drone.pcx', {}), - faction('ANGELS', 'Angels', #to_color(103, 91, 181), #to_color(103, 91, 181), 'angels.pcx', {}), - faction('PLANETCULT', 'Planet Cult', #to_color(232, 84, 84), #to_color(232, 84, 84), 'fungboy.pcx', {}), - faction('CARETAKERS', 'Caretakers', #to_color(116, 156, 56), #to_color(116, 156, 56), 'caretake.pcx', {is_progenitor: true}), - faction('USURPERS', 'Usurpers', #to_color(212, 208, 116), #to_color(212, 208, 116), 'usurper.pcx', {is_progenitor: true}), -]; + define: () => { + for (faction of [ + 'gaians', + 'hive', + 'university', + 'morganites', + 'spartans', + 'believers', + 'peacekeepers', + 'consciousness', + 'pirates', + 'drones', + 'angels', + 'planetcult', + 'caretakers', + 'usurpers' + ]) { + #game.factions.define(#to_uppercase(faction), #include('./factions/' + faction)); + } + }, -return factions; +}; diff --git a/GLSMAC_data/default/factions/angels.gls.js b/GLSMAC_data/default/factions/angels.gls.js new file mode 100644 index 00000000..90f4c8d2 --- /dev/null +++ b/GLSMAC_data/default/factions/angels.gls.js @@ -0,0 +1,14 @@ +return { + name: 'Angels', + colors: #game.factions.import_colors('angels.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'angels.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('angels.txt'), + }, +}; diff --git a/GLSMAC_data/default/factions/believers.gls.js b/GLSMAC_data/default/factions/believers.gls.js new file mode 100644 index 00000000..7383f124 --- /dev/null +++ b/GLSMAC_data/default/factions/believers.gls.js @@ -0,0 +1,14 @@ +return { + name: 'Believers', + colors: #game.factions.import_colors('believe.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'believe.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('believe.txt'), + }, +}; diff --git a/GLSMAC_data/default/factions/caretakers.gls.js b/GLSMAC_data/default/factions/caretakers.gls.js new file mode 100644 index 00000000..021223a8 --- /dev/null +++ b/GLSMAC_data/default/factions/caretakers.gls.js @@ -0,0 +1,15 @@ +return { + name: 'Caretakers', + colors: #game.factions.import_colors('caretake.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'caretake.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('caretake.txt'), + }, + is_progenitor: true, +}; diff --git a/GLSMAC_data/default/factions/consciousness.gls.js b/GLSMAC_data/default/factions/consciousness.gls.js new file mode 100644 index 00000000..4a8bdfc8 --- /dev/null +++ b/GLSMAC_data/default/factions/consciousness.gls.js @@ -0,0 +1,14 @@ +return { + name: 'Consciousness', + colors: #game.factions.import_colors('cyborg.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'cyborg.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('cyborg.txt'), + }, +}; diff --git a/GLSMAC_data/default/factions/drones.gls.js b/GLSMAC_data/default/factions/drones.gls.js new file mode 100644 index 00000000..bf70c73a --- /dev/null +++ b/GLSMAC_data/default/factions/drones.gls.js @@ -0,0 +1,14 @@ +return { + name: 'Drones', + colors: #game.factions.import_colors('drone.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'drone.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('drone.txt'), + }, +}; diff --git a/GLSMAC_data/default/factions/gaians.gls.js b/GLSMAC_data/default/factions/gaians.gls.js new file mode 100644 index 00000000..1b944258 --- /dev/null +++ b/GLSMAC_data/default/factions/gaians.gls.js @@ -0,0 +1,16 @@ +const faction = { + name: 'Gaians', + colors: #game.factions.import_colors('gaians.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'gaians.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('gaians.txt'), + }, +}; + +return faction; \ No newline at end of file diff --git a/GLSMAC_data/default/factions/hive.gls.js b/GLSMAC_data/default/factions/hive.gls.js new file mode 100644 index 00000000..b05b73b9 --- /dev/null +++ b/GLSMAC_data/default/factions/hive.gls.js @@ -0,0 +1,14 @@ +return { + name: 'Hive', + colors: #game.factions.import_colors('hive.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'hive.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('hive.txt'), + }, +}; diff --git a/GLSMAC_data/default/factions/morganites.gls.js b/GLSMAC_data/default/factions/morganites.gls.js new file mode 100644 index 00000000..4618b49f --- /dev/null +++ b/GLSMAC_data/default/factions/morganites.gls.js @@ -0,0 +1,14 @@ +return { + name: 'Morganites', + colors: #game.factions.import_colors('morgan.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'morgan.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('morgan.txt'), + }, +}; diff --git a/GLSMAC_data/default/factions/peacekeepers.gls.js b/GLSMAC_data/default/factions/peacekeepers.gls.js new file mode 100644 index 00000000..486f76e1 --- /dev/null +++ b/GLSMAC_data/default/factions/peacekeepers.gls.js @@ -0,0 +1,14 @@ +return { + name: 'Peacekeepers', + colors: #game.factions.import_colors('peace.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'peace.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('peace.txt'), + }, +}; diff --git a/GLSMAC_data/default/factions/pirates.gls.js b/GLSMAC_data/default/factions/pirates.gls.js new file mode 100644 index 00000000..e3c92643 --- /dev/null +++ b/GLSMAC_data/default/factions/pirates.gls.js @@ -0,0 +1,15 @@ +return { + name: 'Pirates', + colors: #game.factions.import_colors('pirates.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'pirates.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('pirates.txt'), + }, + is_naval: true, +}; diff --git a/GLSMAC_data/default/factions/planetcult.gls.js b/GLSMAC_data/default/factions/planetcult.gls.js new file mode 100644 index 00000000..874201c0 --- /dev/null +++ b/GLSMAC_data/default/factions/planetcult.gls.js @@ -0,0 +1,14 @@ +return { + name: 'Planet Cult', + colors: #game.factions.import_colors('fungboy.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'fungboy.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('fungboy.txt'), + }, +}; diff --git a/GLSMAC_data/default/factions/spartans.gls.js b/GLSMAC_data/default/factions/spartans.gls.js new file mode 100644 index 00000000..df015be8 --- /dev/null +++ b/GLSMAC_data/default/factions/spartans.gls.js @@ -0,0 +1,14 @@ +return { + name: 'Spartans', + colors: #game.factions.import_colors('spartans.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'spartans.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('spartans.txt'), + }, +}; diff --git a/GLSMAC_data/default/factions/university.gls.js b/GLSMAC_data/default/factions/university.gls.js new file mode 100644 index 00000000..55453a74 --- /dev/null +++ b/GLSMAC_data/default/factions/university.gls.js @@ -0,0 +1,14 @@ +return { + name: 'University', + colors: #game.factions.import_colors('univ.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'univ.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('univ.txt'), + }, +}; diff --git a/GLSMAC_data/default/factions/usurpers.gls.js b/GLSMAC_data/default/factions/usurpers.gls.js new file mode 100644 index 00000000..bddd7574 --- /dev/null +++ b/GLSMAC_data/default/factions/usurpers.gls.js @@ -0,0 +1,15 @@ +return { + name: 'Usurpers', + colors: #game.factions.import_colors('usurper.pcx'), + bases: { + render: { + type: 'sprite_grid', + file: 'usurper.pcx', + grid_x: 1, grid_y: 1, + cell_width: 100, cell_height: 75, + cell_padding: 1, + }, + names: #game.factions.import_base_names('usurper.txt'), + }, + is_progenitor: true, +}; diff --git a/GLSMAC_data/default/main.gls.js b/GLSMAC_data/default/main.gls.js index 4fe5117e..4439d8d9 100644 --- a/GLSMAC_data/default/main.gls.js +++ b/GLSMAC_data/default/main.gls.js @@ -1,17 +1,12 @@ const units = #include('units'); +const factions = #include('factions'); #game.on.configure((e) => { - const factions = #include('factions'); - let i = 0; - let sz = #size(factions); - while (i < sz) { - #game.factions.define(factions[i][0], factions[i][1]); - i++; - } + factions.define(); - const rules = #include('rules'); // TODO + const rules = #include('rules'); }); @@ -44,21 +39,19 @@ units.init(); // spawn units let units_spawned = 0; + let bases_spawned = 0; - let y = 0; let w = #game.map.get_width(); let h = #game.map.get_height(); - while (y < h) { - let x = 0; - while (x < w) { + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { if (x % 2 == y % 2) { + let owner = random_player(); + let tile = #game.map.get_tile(x, y); if (#game.random.get_int(0, 6) == 0) { - let owner = random_player(); - let tile = #game.map.get_tile(x, y); let units_count = #game.random.get_int(1, 2); - let i = 0; - while (i++ < units_count) { + for (let i = 0; i < units_count; i++) { if (tile.is_land) { if (#game.random.get_int(0, 4) != 0) { #game.units.spawn('MindWorms', owner, tile, random_morale(), random_health()); @@ -67,14 +60,13 @@ units.init(); if (tile.has_fungus && #game.random.get_int(0, 3) == 0) { // morale depends on count of fungus tiles around let morale = 1; - let neighbours = tile.get_surrounding_tiles(); - let sz = #size(neighbours); - let i = 0; - while (morale < 6 && i < sz) { - if (neighbours[i].has_fungus) { + for (neighbour of tile.get_surrounding_tiles()) { + if (morale >= 6) { + break; + } + if (neighbour.has_fungus) { morale++; } - i++; } #game.units.spawn('FungalTower', owner, tile, morale, random_health()); units_spawned++; @@ -91,29 +83,31 @@ units.init(); } } } + if (#game.random.get_int(0, 2) == 0) { + let has_adjactent_bases = false; + for (neighbour of tile.get_surrounding_tiles()) { + if (neighbour.get_base() != null) { + has_adjactent_bases = true; + break; + } + } + if (!has_adjactent_bases) { + #game.bases.spawn( + owner, + tile, + { + // name: 'base name', + population: #game.random.get_int(0, 4) * #game.random.get_int(0, 4) + #game.random.get_int(1, 3), + } + ); + bases_spawned++; + } + } } - x++; } - y++; } #game.message('Total units spawned: ' + #to_string(units_spawned)); - - // spawn bases - - let i = 0; - while (i < players_sz) { - let base_x = 0; - let base_y = 1; - while (base_x % 2 != base_y % 2) { - base_x = #game.random.get_int(0, w - 1); - base_y = #game.random.get_int(0, h - 1); - } - #game.bases.spawn( - players[i], - #game.map.get_tile(base_x, base_y) - ); - i++; - } + #game.message('Total bases spawned: ' + #to_string(bases_spawned)); }); diff --git a/GLSMAC_data/default/units/animations.gls.js b/GLSMAC_data/default/units/animations.gls.js index 749dedcf..abc15e55 100644 --- a/GLSMAC_data/default/units/animations.gls.js +++ b/GLSMAC_data/default/units/animations.gls.js @@ -38,11 +38,8 @@ const result = { ]; - let sz = #size(animations); - let i = 0; - while (i < sz) { - #game.animations.define(animations[i][0], animations[i][1]); - i++; + for (animation of animations) { + #game.animations.define(animation[0], animation[1]); } }, diff --git a/GLSMAC_data/default/units/defs.gls.js b/GLSMAC_data/default/units/defs.gls.js index c301a735..220e47ec 100644 --- a/GLSMAC_data/default/units/defs.gls.js +++ b/GLSMAC_data/default/units/defs.gls.js @@ -41,20 +41,12 @@ const units = [ const result = { define: () => { - // TODO: for loops - - let i = 0; - let sz = #size(morales); - while (i < sz) { - #game.units.define_morales(morales[i][0], morales[i][1]); - i++; + for (morale of morales) { + #game.units.define_morales(morale[0], morale[1]); } - i = 0; - sz = #size(units); - while (i < sz) { - #game.units.define(units[i][0], units[i][1]); - i++; + for (unit of units) { + #game.units.define(unit[0], unit[1]); } }, diff --git a/GLSMAC_data/default/units/movement.gls.js b/GLSMAC_data/default/units/movement.gls.js index 9232d023..264ca18a 100644 --- a/GLSMAC_data/default/units/movement.gls.js +++ b/GLSMAC_data/default/units/movement.gls.js @@ -57,15 +57,12 @@ const result = { return 'Water units can\'t move to land tile'; } - const units = e.dst_tile.get_units(); - let i = 0; let any_foreign_units_in_tile = false; - while (i < #size(units)) { // TODO: for loop - if (units[i].get_owner() != e.unit.get_owner()) { + for (unit of e.dst_tile.get_units()) { + if (unit.get_owner() != e.unit.get_owner()) { any_foreign_units_in_tile = true; - // TODO: break + break; } - i++; } if (any_foreign_units_in_tile) { return 'Destination tile contains foreign units (combat not implemented yet)'; diff --git a/GLSMAC_data/tests/types.gls.js b/GLSMAC_data/tests/types.gls.js index ec3afa7d..b6b9cd4c 100644 --- a/GLSMAC_data/tests/types.gls.js +++ b/GLSMAC_data/tests/types.gls.js @@ -13,6 +13,18 @@ let c = (a + 2) * 4; c = 123; c -= 23; +let emptymethod = () => {}; +test.assert(#typeof(emptymethod()) == 'Undefined'); + +let ret = 0; +let testmethod0 = () => { + ret = 1; + return; + ret = 2; +}; +test.assert(#typeof(testmethod0()) == 'Undefined'); +test.assert(ret == 1); + let testmethod1 = (a, b, c) => { return a + b + c; }; @@ -43,29 +55,9 @@ testarr1 [] = testarr2; let testarr3 = testarr1; testarr3[1] = 'SECOND'; testarr3[testmethod2(a, b, c) + 61] = 'FIRST'; -testarr3[2 -: -5 -] -= testarr1[0 -: -1 -] -+testarr2[0 -: -1 -] -; -let testarr4 = testarr3[ -: -3 -] -; -testarr4[c + 1 - 100 -: -c - 100 + 2 -] -= ['new first', 'new second']; +testarr3[2:5] = testarr1[0:1] + testarr2[0:1]; +let testarr4 = testarr3[:3]; +testarr4[ c + 1 - 100 : c - 100 + 2 ] = ['new first', 'new second']; let testobj1 = {}; let testobj2 = { @@ -164,39 +156,11 @@ test.assert(testarr4[1] == 'new first'); test.assert(testarr4[2] == 'new second'); test.assert(testarr4[3] == 'second'); -test.assert(testarr1[0 -: -1 -] == -['first', 'second'] -) -; +test.assert(testarr1[0:1] == ['first', 'second']); -test.assert(testarr1[5 -:] == -[testarr1[5], testarr1[6]] -) -; -test.assert(testarr1[ -: -3 -] == -[testarr1[0], testarr1[1]] + testarr1[2 -: -3 -] ) -; -test.assert(testarr1[4 -: -5 -] -+testarr1[2 -: -3 -] == -[testarr1[4], testarr1[5], testarr1[2], testarr1[3]] -) -; +test.assert(testarr1[5:] == [testarr1[5], testarr1[6]]); +test.assert(testarr1[:3] == [testarr1[0], testarr1[1]] + testarr1[2:3] ); +test.assert(testarr1[4:5] + testarr1[2:3] == [testarr1[4], testarr1[5], testarr1[2], testarr1[3]]); test.assert(testobj3.child1.child2.value == 'CHILD VALUE'); test.assert(testobj1.propertyInt == 272 + c); test.assert(testobj1 == {propertyInt: 372}); @@ -248,6 +212,128 @@ while (i++ < 5) { } test.assert(arr == [1, 2, 3, 4, 5]); +const data = ['a', 'b', 'c']; + +arr = []; +for (i in data) { + arr []= #to_string(i) + '_' + data[i]; +} +test.assert(arr == ['0_a', '1_b', '2_c']); +for (i in ['asd', 'qwe', 'zxc']) { + arr []= i; +} +test.assert(arr == ['0_a', '1_b', '2_c', 0, 1, 2]); +for (i in { + key1: 'value1', + key2: 'value2', +}) { + arr []= i; +} +test.assert(arr == ['0_a', '1_b', '2_c', 0, 1, 2, 'key1', 'key2']); + +arr = []; +for (v of data) { + arr []= 'of_' + v; +} +test.assert(arr == ['of_a', 'of_b', 'of_c']); +for (v of ['asd', 'qwe', 'zxc', (5 + 10), {k: 'v'}, ((x) => { return x * 2 })(5)]) { + arr []= v; +} +test.assert(arr == [ 'of_a', 'of_b', 'of_c', 'asd', 'qwe', 'zxc', 15, {k: 'v'}, 10]); +for (i of { + key1: 'value1', + key2: 'value2', +}) { + arr []= i; +} +test.assert(arr == [ 'of_a', 'of_b', 'of_c', 'asd', 'qwe', 'zxc', 15, {k: 'v'}, 10, 'value1', 'value2']); + +arr = []; +for (let ii = 2 ; ii > 0 ; ii--) { + arr []= 'i_' + data[ii]; +} +test.assert(arr == ['i_c', 'i_b']); + +for (i = 5 ; i <= 10 ; i++) { + arr []= i; +} +test.assert(arr == ['i_c', 'i_b', 5, 6, 7, 8, 9, 10 ]); + +const for_func = () => { + for ( i = 0 ; i < 10 ; i++ ) { + if ( i == 6 ) { + return i; + } + } +}; +test.assert(for_func() == 6); + +arr = []; +for ( i = 0 ; i < 10 ; i++ ) { + arr []= i; + if (i >= 3) { + arr []= 'x'; + for ( i = 5 ; i < 10 ; i++ ) { + if ( i == 7 ) { + break; + } + arr []= i; + } + arr []= 'y'; + break; + } +} +test.assert(arr == [0, 1, 2, 3, 'x', 5, 6, 'y']); + +arr = []; +i = 5; +while (i > 0) { + if (i == 2 ) { + break; + } + arr []= i; + i--; +} +test.assert(arr == [5, 4, 3]); + +arr = []; +for ( i of [4, 7, 1, 5] ) { + if ( i == 1 ) { + break; + } + arr []= i; +} +test.assert(arr == [4, 7]); + +arr = []; +for ( i = 0 ; i < 10 ; i++ ) { + if ( i < 5 || i > 8 ) { + continue; + } + arr []= i; +} +test.assert(arr == [5, 6, 7, 8]); + +arr = []; +i = 10; +while (i > 0) { + i--; + if (i > 7 || i < 3) { + for ( ii of ['a', 'b', 'c', 'd', 'e', 'f'] ) { + if ( ii == 'a' || ii == 'c' ) { + continue; + } + elseif (ii == 'e') { + break; + } + arr []= ii; + } + continue; + } + arr []= i; +} +test.assert(arr == ['b', 'd', 'b', 'd', 7, 6, 5, 4, 3, 'b', 'd', 'b', 'd', 'b', 'd']); + arr = []; try { arr [] = 'BEFORE EXCEPTION'; // should be printed @@ -273,16 +359,16 @@ try { arr [] = 'CAUGHT ' + e.type + ' : ' + e.reason; arr += e.backtrace; } -} -; +}; + test.assert(arr == [ 'BEFORE EXCEPTION', 'failfunc', 'failfunc2', 'CAUGHT TestError : something happened', - '\tat ' + test.get_script_path() + ':257: throw TestError(\'something happened\');', - '\tat ' + test.get_script_path() + ':262: realfailfunc();', - '\tat ' + test.get_script_path() + ':264: failfunc();' + '\tat ' + test.get_script_path() + ':343: throw TestError(\'something happened\');', + '\tat ' + test.get_script_path() + ':348: realfailfunc();', + '\tat ' + test.get_script_path() + ':350: failfunc();' ]); test.assert(#to_string(2 + 3) + ' (five)' == '5 (five)'); @@ -301,16 +387,9 @@ test.assert(#typeof(123.) == 'Float'); test.assert(#typeof(0.123) == 'Float'); test.assert(#typeof('string') == 'String'); test.assert(#typeof([]) == 'Array'); -test.assert(#typeof(2 -: -3 -) == -'Range' -) -; +test.assert(#typeof(2:3) == 'Range'); test.assert(#typeof({}) == 'Object'); -test.assert(#typeof(() => { -}) == 'Callable'); +test.assert(#typeof(() => {}) == 'Callable'); test.assert(15.0 != 15); test.assert(#round(15.0) == 15); diff --git a/src/config/Config.cpp b/src/config/Config.cpp index cca96dc9..00b9ef38 100644 --- a/src/config/Config.cpp +++ b/src/config/Config.cpp @@ -41,8 +41,9 @@ const types::Vec2< size_t > Config::ParseSize( const std::string& value ) { Config::Config( const int argc, const char* argv[] ) : m_smac_path( "" ) + , m_data_path( "GLSMAC_data" ) , m_prefix( DEFAULT_GLSMAC_PREFIX + util::FS::PATH_SEPARATOR ) { - + m_parser = new util::ArgParser( argc, argv ); m_parser->AddRule( @@ -51,8 +52,8 @@ Config::Config( const int argc, const char* argv[] ) } ); m_parser->AddRule( - "showfps", "Show FPS counter at top left corner", AH( this ) { - m_launch_flags |= LF_SHOWFPS; + "datapath", "DATA_PATH", "Specify path to GLSMAC_data directory (default: " + util::FS::GetCurrentDirectory() + "/" + m_data_path + ")", AH( this ) { + m_data_path = value; } ); m_parser->AddRule( @@ -76,6 +77,11 @@ Config::Config( const int argc, const char* argv[] ) m_launch_flags |= LF_SKIPINTRO; } ); + m_parser->AddRule( + "showfps", "Show FPS counter at top left corner", AH( this ) { + m_launch_flags |= LF_SHOWFPS; + } + ); m_parser->AddRule( "smacpath", "SMAC_PATH", "Specify path to SMAC directory (must contain SMACX too)", AH( this ) { m_smac_path = value; @@ -309,6 +315,10 @@ const std::string Config::GetDebugPath() const { #endif +const std::string& Config::GetDataPath() const { + return m_data_path; +} + const std::vector< std::string > Config::GetPossibleSMACPaths() const { std::vector< std::string > result = {}; if ( !m_smac_path.empty() ) { diff --git a/src/config/Config.h b/src/config/Config.h index 80b05da2..ea52feb7 100644 --- a/src/config/Config.h +++ b/src/config/Config.h @@ -57,7 +57,7 @@ CLASS( Config, common::Module ) const std::string GetEnv( const std::string& var ) const; const std::string& GetPrefix() const; - + const std::string& GetDataPath() const; const std::vector< std::string > GetPossibleSMACPaths() const; #ifdef DEBUG @@ -100,6 +100,7 @@ CLASS( Config, common::Module ) ; std::string m_prefix; + std::string m_data_path; std::string m_smac_path; uint8_t m_launch_flags = LF_NONE; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 667e2413..cbb11075 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -10,6 +10,7 @@ #include "loader/font/FontLoader.h" #include "loader/texture/TextureLoader.h" #include "loader/sound/SoundLoader.h" +#include "loader/txt/TXTLoaders.h" #include "scheduler/Scheduler.h" #include "input/Input.h" #include "graphics/Graphics.h" @@ -33,6 +34,7 @@ Engine::Engine( loader::font::FontLoader* font_loader, loader::texture::TextureLoader* texture_loader, loader::sound::SoundLoader* sound_loader, + loader::txt::TXTLoaders* txt_loaders, scheduler::Scheduler* scheduler, input::Input* input, graphics::Graphics* graphics, @@ -49,6 +51,7 @@ Engine::Engine( , m_font_loader( font_loader ) , m_texture_loader( texture_loader ) , m_sound_loader( sound_loader ) + , m_txt_loaders( txt_loaders ) , m_scheduler( scheduler ) , m_input( input ) , m_graphics( graphics ) diff --git a/src/engine/Engine.h b/src/engine/Engine.h index 1416e355..ae44c68d 100644 --- a/src/engine/Engine.h +++ b/src/engine/Engine.h @@ -39,6 +39,9 @@ class TextureLoader; namespace sound { class SoundLoader; } +namespace txt { +class TXTLoaders; +} } namespace input { @@ -81,6 +84,7 @@ CLASS( Engine, common::Class ); loader::font::FontLoader* font_loader, loader::texture::TextureLoader* texture_loader, loader::sound::SoundLoader* sound_loader, + loader::txt::TXTLoaders* txt_loaders, scheduler::Scheduler* scheduler, input::Input* input, graphics::Graphics* graphics, @@ -100,6 +104,7 @@ CLASS( Engine, common::Class ); loader::font::FontLoader* GetFontLoader() const { return m_font_loader; } loader::texture::TextureLoader* GetTextureLoader() const { return m_texture_loader; } loader::sound::SoundLoader* GetSoundLoader() const { return m_sound_loader; } + loader::txt::TXTLoaders* GetTXTLoaders() const { return m_txt_loaders; } input::Input* GetInput() const { return m_input; } graphics::Graphics* GetGraphics() const { return m_graphics; } audio::Audio* GetAudio() const { return m_audio; } @@ -121,6 +126,7 @@ CLASS( Engine, common::Class ); loader::font::FontLoader* m_font_loader = nullptr; loader::texture::TextureLoader* m_texture_loader = nullptr; loader::sound::SoundLoader* m_sound_loader = nullptr; + loader::txt::TXTLoaders* m_txt_loaders = nullptr; scheduler::Scheduler* m_scheduler = nullptr; input::Input* m_input = nullptr; graphics::Graphics* m_graphics = nullptr; diff --git a/src/env/Win32.h b/src/env/Win32.h index eb43375d..d5bdf13c 100644 --- a/src/env/Win32.h +++ b/src/env/Win32.h @@ -7,10 +7,12 @@ #include typedef SSIZE_T ssize_t; #endif +#if !defined(NOMINMAX) #define NOMINMAX +#endif #define WIN32_LEAN_AND_MEAN -#define _WINSOCKAPI_ +#define _WINSOCKAPI_ #include // TODO: check if compiles with mingw #include diff --git a/src/game/FrontendRequest.cpp b/src/game/FrontendRequest.cpp index 26d26ff2..28adba44 100644 --- a/src/game/FrontendRequest.cpp +++ b/src/game/FrontendRequest.cpp @@ -59,6 +59,10 @@ FrontendRequest::FrontendRequest( const FrontendRequest& other ) NEW( data.unit_spawn.morale_string, std::string, *other.data.unit_spawn.morale_string ); break; } + case FR_BASE_SPAWN: { + NEW( data.base_spawn.base_info.name, std::string, *other.data.base_spawn.base_info.name ); + break; + } default: { // @@ -110,6 +114,10 @@ FrontendRequest::~FrontendRequest() { DELETE( data.unit_spawn.morale_string ); break; } + case FR_BASE_SPAWN: { + DELETE( data.base_spawn.base_info.name ); + break; + } default: { // } diff --git a/src/game/FrontendRequest.h b/src/game/FrontendRequest.h index 12bcb17b..2a8f9ebb 100644 --- a/src/game/FrontendRequest.h +++ b/src/game/FrontendRequest.h @@ -151,6 +151,10 @@ class FrontendRequest { float y; float z; } render_coords; + struct { + const std::string* name; + size_t population; + } base_info; } base_spawn; } data; }; diff --git a/src/game/Game.cpp b/src/game/Game.cpp index c5a94af7..337aafd6 100644 --- a/src/game/Game.cpp +++ b/src/game/Game.cpp @@ -925,13 +925,42 @@ void Game::SpawnBase( base::Base* base ) { return; } - Log( "Spawning base #" + std::to_string( base->m_id ) + " at " + base->GetTile()->ToString() ); + auto* tile = base->GetTile(); + + // validate and fix name if needed (or assign if empty) + std::vector< std::string > names_to_try = {}; + if ( base->m_data.name.empty() ) { + const auto& names = base->m_owner->GetPlayer()->GetFaction()->m_base_names; + names_to_try = tile->is_water_tile + ? names.water + : names.land; + } + else if ( m_registered_base_names.find( base->m_data.name ) != m_registered_base_names.end() ) { + names_to_try = { base->m_data.name }; + } + if ( !names_to_try.empty() ) { + size_t cycle = 0; + bool found = false; + while ( !found ) { + cycle++; + for ( const auto& name_to_try : names_to_try ) { + base->m_data.name = cycle == 1 + ? name_to_try + : name_to_try + " " + std::to_string( cycle ); + if ( m_registered_base_names.find( base->m_data.name ) == m_registered_base_names.end() ) { + found = true; + break; + } + } + } + } + m_registered_base_names.insert( base->m_data.name ); + + Log( "Spawning base #" + std::to_string( base->m_id ) + " ( " + base->m_data.name + ", " + std::to_string( base->m_data.population ) + " ) at " + base->GetTile()->ToString() ); ASSERT( m_bases.find( base->m_id ) == m_bases.end(), "duplicate base id" ); m_bases.insert_or_assign( base->m_id, base ); - auto* tile = base->GetTile(); - QueueBaseUpdate( base, BUO_SPAWN ); if ( m_state->IsMaster() ) { @@ -944,6 +973,8 @@ void Game::SpawnBase( base::Base* base ) { } ); } + + RefreshBase( base ); } const std::string* Game::MoveUnitValidate( unit::Unit* unit, map::tile::Tile* dst_tile ) { @@ -2098,6 +2129,8 @@ void Game::PushBaseUpdates() { c.y, c.z }; + NEW( fr.data.base_spawn.base_info.name, std::string, base->m_data.name ); + fr.data.base_spawn.base_info.population = base->m_data.population; AddFrontendRequest( fr ); } if ( bu.ops & BUO_REFRESH ) { diff --git a/src/game/Game.h b/src/game/Game.h index 93828872..22331350 100644 --- a/src/game/Game.h +++ b/src/game/Game.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include @@ -331,6 +333,7 @@ CLASS( Game, MTModule ) void SpawnUnit( unit::Unit* unit ); void SkipUnitTurn( const size_t unit_id ); void DespawnUnit( const size_t unit_id ); + std::string RegisterBaseName( const std::string& requested_name ); void SpawnBase( base::Base* base ); const std::string* MoveUnitValidate( unit::Unit* unit, map::tile::Tile* dst_tile ); const gse::Value MoveUnitResolve( unit::Unit* unit, map::tile::Tile* dst_tile ); @@ -468,6 +471,8 @@ CLASS( Game, MTModule ) std::vector< std::pair< map::tile::positions_t, cb_oncomplete > > m_tile_lock_callbacks = {}; // tile positions (for matching), callback + std::unordered_set< std::string > m_registered_base_names = {}; + private: friend class bindings::Bindings; void PushUnitUpdates(); diff --git a/src/game/base/Base.cpp b/src/game/base/Base.cpp index c91a640a..8de0320e 100644 --- a/src/game/base/Base.cpp +++ b/src/game/base/Base.cpp @@ -25,27 +25,29 @@ Base::Base( Game* game, const size_t id, slot::Slot* owner, - map::tile::Tile* tile + map::tile::Tile* tile, + const BaseData& data ) : MapObject( game->GetMap(), tile ) , m_game( game ) , m_id( id ) - , m_owner( owner ) { + , m_owner( owner ) + , m_data( data ) { if ( next_id <= id ) { next_id = id + 1; } ASSERT_NOLOG( !tile->base, "tile already has base" ); tile->base = this; m_tile = tile; - m_game->RefreshBase( this ); } -const types::Buffer Base::Serialize( const Base* unit ) { +const types::Buffer Base::Serialize( const Base* base ) { types::Buffer buf; - buf.WriteInt( unit->m_id ); - buf.WriteInt( unit->m_owner->GetIndex() ); - buf.WriteInt( unit->m_tile->coord.x ); - buf.WriteInt( unit->m_tile->coord.y ); + buf.WriteInt( base->m_id ); + buf.WriteInt( base->m_owner->GetIndex() ); + buf.WriteInt( base->m_tile->coord.x ); + buf.WriteInt( base->m_tile->coord.y ); + base->m_data.Serialize( buf ); return buf; } @@ -55,7 +57,8 @@ Base* Base::Unserialize( types::Buffer& buf, Game* game ) { const auto pos_x = buf.ReadInt(); const auto pos_y = buf.ReadInt(); auto* tile = game ? game->GetMap()->GetTile( pos_x, pos_y ) : nullptr; - return new Base( game, id, slot, tile ); + const auto data = BaseData( buf ); + return new Base( game, id, slot, tile, data ); } WRAPIMPL_DYNAMIC_GETTERS( Base, CLASS_BASE ) diff --git a/src/game/base/Base.h b/src/game/base/Base.h index b10732e4..ad03a1f9 100644 --- a/src/game/base/Base.h +++ b/src/game/base/Base.h @@ -2,6 +2,8 @@ #include +#include "BaseData.h" + #include "gse/Wrappable.h" #include "game/MapObject.h" @@ -29,14 +31,17 @@ class Base : public gse::Wrappable, public MapObject { Game* game, const size_t id, slot::Slot* owner, - map::tile::Tile* tile + map::tile::Tile* tile, + const BaseData& data ); virtual ~Base() = default; const size_t m_id; slot::Slot* m_owner; - static const types::Buffer Serialize( const Base* unit ); + BaseData m_data; + + static const types::Buffer Serialize( const Base* base ); static Base* Unserialize( types::Buffer& buf, Game* game ); WRAPDEFS_DYNAMIC( Base ); diff --git a/src/game/base/BaseData.cpp b/src/game/base/BaseData.cpp new file mode 100644 index 00000000..0f900cb9 --- /dev/null +++ b/src/game/base/BaseData.cpp @@ -0,0 +1,34 @@ +#include "BaseData.h" + +namespace game { +namespace base { + +BaseData::BaseData( const std::string& name, const size_t population ) + : name( name ) + , population( population ) { + // +} + +BaseData::BaseData( types::Buffer& buf ) { + Unserialize( buf ); +} + +void BaseData::Serialize( types::Buffer& buf ) const { + buf.WriteString( name ); + buf.WriteInt( population ); +} + +void BaseData::Unserialize( types::Buffer& buf ) { + name = buf.ReadString(); + population = buf.ReadInt(); +} + +TS_BEGIN( BaseData ) + TS_FUNC_BEGIN( "BaseData" ) + + TS_OBJ_PROP_STR( "name", name ) + + TS_OBJ_PROP_NUM( "population", population ) + + TS_FUNC_END() +TS_END() + +} +} diff --git a/src/game/base/BaseData.h b/src/game/base/BaseData.h new file mode 100644 index 00000000..93931ae7 --- /dev/null +++ b/src/game/base/BaseData.h @@ -0,0 +1,30 @@ +#pragma once + +#include "gse/Wrappable.h" +#include "gse/Value.h" + +#include "types/Buffer.h" + +namespace game { +namespace base { + +class BaseData : public gse::Wrappable { +public: + + BaseData( const std::string& name, const size_t population ); + BaseData( types::Buffer& buf ); + + std::string name; + size_t population; + + void Serialize( types::Buffer& buf ) const; + void Unserialize( types::Buffer& buf ); + + const std::string ToString( const std::string& prefix = "" ) const; + + WRAPDEFS_DYNAMIC( BaseData ); + +}; + +} +} diff --git a/src/game/base/CMakeLists.txt b/src/game/base/CMakeLists.txt index c4dcf676..74476b9c 100644 --- a/src/game/base/CMakeLists.txt +++ b/src/game/base/CMakeLists.txt @@ -1,5 +1,6 @@ SET( SRC ${SRC} ${PWD}/Base.cpp + ${PWD}/BaseData.cpp PARENT_SCOPE ) diff --git a/src/game/bindings/Bases.cpp b/src/game/bindings/Bases.cpp index 74df46aa..f81f059e 100644 --- a/src/game/bindings/Bases.cpp +++ b/src/game/bindings/Bases.cpp @@ -6,6 +6,8 @@ #include "gse/Exception.h" #include "gse/type/Undefined.h" #include "gse/type/Object.h" +#include "gse/type/Int.h" +#include "gse/type/String.h" #include "game/Game.h" #include "game/slot/Slot.h" #include "game/map/tile/Tile.h" @@ -19,15 +21,21 @@ BINDING_IMPL( bases ) { { "spawn", NATIVE_CALL( this ) { - N_EXPECT_ARGS( 2 ); + N_EXPECT_ARGS( 3 ); N_GETVALUE_UNWRAP( owner, 0, slot::Slot ); N_GETVALUE_UNWRAP( tile, 1, map::tile::Tile ); + + N_GETVALUE( info, 2, Object ); + N_GETPROP_OPT( std::string, name, info, "name", String, "" ); + N_GETPROP_OPT( size_t, population, info, "population", Int, 1 ); + auto* game = GAME; game->AddEvent( new event::SpawnBase( game->GetSlotNum(), owner->GetIndex(), tile->coord.x, - tile->coord.y + tile->coord.y, + base::BaseData( name, population ) ) ); return VALUE( gse::type::Undefined ); }) diff --git a/src/game/bindings/Bindings.cpp b/src/game/bindings/Bindings.cpp index 970811e2..5a7fa1aa 100644 --- a/src/game/bindings/Bindings.cpp +++ b/src/game/bindings/Bindings.cpp @@ -11,7 +11,8 @@ #include "gse/type/Object.h" #include "gse/type/Callable.h" #include "gse/type/Undefined.h" - +#include "engine/Engine.h" +#include "config/Config.h" #include "game/State.h" namespace game { @@ -22,7 +23,7 @@ Bindings::Bindings( State* state ) , m_entry_script( util::FS::GeneratePath( { - "GLSMAC_data", // directory is expected to be in working dir + g_engine->GetConfig()->GetDataPath(), "default", // only 'default' mod for now "main" // script name (extension is appended automatically) }, gse::GSE::PATH_SEPARATOR @@ -84,6 +85,10 @@ gse::Value Bindings::Call( const callback_slot_t slot, const callback_arguments_ if ( game ) { game->PushUnitUpdates(); } + if ( result.Get()->type == gse::type::Type::T_NOTHING ) { + // return undefined by default + return VALUE( gse::type::Undefined ); + } return result; } catch ( gse::Exception& e ) { diff --git a/src/game/bindings/Factions.cpp b/src/game/bindings/Factions.cpp index d3c364fd..b2419616 100644 --- a/src/game/bindings/Factions.cpp +++ b/src/game/bindings/Factions.cpp @@ -3,6 +3,7 @@ #include "gse/callable/Native.h" #include "gse/Exception.h" #include "gse/type/Object.h" +#include "gse/type/Array.h" #include "gse/type/String.h" #include "gse/type/Int.h" #include "gse/type/Float.h" @@ -10,6 +11,11 @@ #include "gse/type/Bool.h" #include "game/bindings/Bindings.h" #include "game/State.h" +#include "engine/Engine.h" +#include "loader/txt/TXTLoaders.h" +#include "loader/txt/FactionTXTLoader.h" +#include "loader/texture/TextureLoader.h" +#include "types/texture/Texture.h" #include "types/Color.h" @@ -20,9 +26,50 @@ BINDING_IMPL( factions ) { auto& factions = m_bindings->GetState()->m_settings.global.game_rules.m_factions; auto& factions_order = m_bindings->GetState()->m_settings.global.game_rules.m_factions_order; const gse::type::object_properties_t properties = { + { + "import_base_names", + NATIVE_CALL() { + N_EXPECT_ARGS( 1 ); + N_GETVALUE( filename, 0, String ); + const auto& data = g_engine->GetTXTLoaders()->factions->GetFactionData( filename ); + std::vector< gse::Value > land_names = {}; + land_names.reserve( data.bases_names.land.size() ); + for ( const auto& name : data.bases_names.land ) { + land_names.push_back( VALUE( gse::type::String, name ) ); + } + std::vector< gse::Value > water_names = {}; + water_names.reserve( data.bases_names.water.size() ); + for ( const auto& name : data.bases_names.water ) { + water_names.push_back( VALUE( gse::type::String, name ) ); + } + const auto properties = gse::type::object_properties_t{ + { "land", VALUE( gse::type::Array, land_names ) }, + { "water", VALUE( gse::type::Array, water_names ) }, + }; + return VALUE( gse::type::Object, properties ); + } ) + }, + { + "import_colors", + NATIVE_CALL() { + N_EXPECT_ARGS( 1 ); + N_GETVALUE( filename, 0, String ); + const auto* texture = g_engine->GetTextureLoader()->LoadCustomTexture( filename ); + const auto properties = gse::type::object_properties_t{ + { "faction", types::Color::FromRGBA( texture->GetPixel( 4, 739 ) ).Wrap() }, + { "faction_shadow", types::Color::FromRGBA( texture->GetPixel( 4, 747 ) ).Wrap() }, + { "text", types::Color::FromRGBA( texture->GetPixel( 4, 755 ) ).Wrap() }, + { "text_shadow", types::Color::FromRGBA( texture->GetPixel( 4, 763 ) ).Wrap() }, + { "border", types::Color::FromRGBA( texture->GetPixel( 161, 749 ) ).Wrap() }, + { "border_alpha", types::Color::FromRGBA( texture->GetPixel( 161, 757 ) ).Wrap() }, + { "vehicle", types::Color::FromRGBA( texture->GetPixel( 435, 744 ) ).Wrap() }, + }; + return VALUE( gse::type::Object, properties ); + } ) + }, { "define", - NATIVE_CALL( this, &factions, &factions_order ) { + NATIVE_CALL( &factions, &factions_order ) { N_EXPECT_ARGS( 2 ); N_GETVALUE( id, 0, String ); if ( factions.find( id ) != factions.end() ) { @@ -35,9 +82,18 @@ BINDING_IMPL( factions ) { N_GETPROP( colors, faction_def, "colors", Object ); N_GETPROP_UNWRAP( colors_text, colors, "text", types::Color ); + N_GETPROP_UNWRAP( colors_text_shadow, colors, "text", types::Color ); N_GETPROP_UNWRAP( colors_border, colors, "border", types::Color ); - faction.m_colors = { colors_text, colors_border }; + faction.m_colors = { + colors_text, + colors_text_shadow, + colors_border + }; + N_GETPROP_OPT_BOOL( is_naval, faction_def, "is_naval") + if ( is_naval ) { + faction.m_flags |= rules::Faction::FF_NAVAL; + } N_GETPROP_OPT_BOOL( is_progenitor, faction_def, "is_progenitor") if ( is_progenitor ) { faction.m_flags |= rules::Faction::FF_PROGENITOR; @@ -75,6 +131,21 @@ BINDING_IMPL( factions ) { ERROR( gse::EC.GAME_ERROR, "Unsupported bases render type: " + bases_render_type ); } + N_GETPROP( base_names, bases_def, "names", Object ); + N_GETPROP( base_names_land, base_names, "land", Array ); + faction.m_base_names.land.reserve( base_names_land.size() ); + for ( size_t i = 0 ; i < base_names_land.size() ; i++ ) { + N_GETELEMENT( v, base_names_land, i, String ); + faction.m_base_names.land.push_back( v ); + } + + N_GETPROP( base_names_water, base_names, "water", Array ); + faction.m_base_names.water.reserve( base_names_water.size() ); + for ( size_t i = 0 ; i < base_names_water.size() ; i++ ) { + N_GETELEMENT( v, base_names_water, i, String ); + faction.m_base_names.water.push_back( v ); + } + factions.insert({ id, faction }); factions_order.push_back( id ); return VALUE( gse::type::Undefined ); diff --git a/src/game/event/SpawnBase.cpp b/src/game/event/SpawnBase.cpp index c0adfec2..3453aadf 100644 --- a/src/game/event/SpawnBase.cpp +++ b/src/game/event/SpawnBase.cpp @@ -16,12 +16,14 @@ SpawnBase::SpawnBase( const size_t initiator_slot, const size_t owner_slot, const size_t pos_x, - const size_t pos_y + const size_t pos_y, + const base::BaseData& data ) : Event( initiator_slot, ET_BASE_SPAWN ) , m_owner_slot( owner_slot ) , m_pos_x( pos_x ) - , m_pos_y( pos_y ) { + , m_pos_y( pos_y ) + , m_data( data ) { // } @@ -41,7 +43,8 @@ const gse::Value SpawnBase::Apply( game::Game* game ) const { game, base::Base::GetNextId(), &owner, - tile + tile, + m_data ); game->SpawnBase( base ); return base->Wrap(); @@ -52,6 +55,7 @@ TS_BEGIN( SpawnBase ) TS_FUNC_ARG_NUM( "owner_slot", m_owner_slot ) + TS_FUNC_ARG_NUM( "pos_x", m_pos_x ) + TS_FUNC_ARG_NUM( "pos_y", m_pos_y ) + + TS_FUNC_ARG_STR( "data", m_data.ToString( TS_PREFIX_NEXT ) ) + TS_FUNC_END() TS_END() @@ -59,13 +63,15 @@ void SpawnBase::Serialize( types::Buffer& buf, const SpawnBase* event ) { buf.WriteInt( event->m_owner_slot ); buf.WriteInt( event->m_pos_x ); buf.WriteInt( event->m_pos_y ); + event->m_data.Serialize( buf ); } SpawnBase* SpawnBase::Unserialize( types::Buffer& buf, const size_t initiator_slot ) { const auto owner_slot = buf.ReadInt(); const auto pos_x = buf.ReadInt(); const auto pos_y = buf.ReadInt(); - return new SpawnBase( initiator_slot, owner_slot, pos_x, pos_y ); + const auto data = base::BaseData( buf ); + return new SpawnBase( initiator_slot, owner_slot, pos_x, pos_y, data ); } } diff --git a/src/game/event/SpawnBase.h b/src/game/event/SpawnBase.h index fe209c5d..8a737e9a 100644 --- a/src/game/event/SpawnBase.h +++ b/src/game/event/SpawnBase.h @@ -1,8 +1,10 @@ #pragma once +#include + #include "Event.h" -#include "game/unit/Types.h" +#include "game/base/BaseData.h" namespace game { namespace event { @@ -13,7 +15,8 @@ class SpawnBase : public Event { const size_t initiator_slot, const size_t owner_slot, const size_t pos_x, - const size_t pos_y + const size_t pos_y, + const base::BaseData& data ); const std::string* Validate( Game* game ) const override; @@ -30,6 +33,7 @@ class SpawnBase : public Event { const size_t m_owner_slot; const size_t m_pos_x; const size_t m_pos_y; + const base::BaseData m_data; }; } diff --git a/src/game/map/tile/Tile.cpp b/src/game/map/tile/Tile.cpp index a2dd36be..e41006c8 100644 --- a/src/game/map/tile/Tile.cpp +++ b/src/game/map/tile/Tile.cpp @@ -4,11 +4,13 @@ #include "gse/type/Array.h" #include "gse/type/Int.h" #include "gse/type/Bool.h" +#include "gse/type/Null.h" #include "gse/type/Undefined.h" #include "gse/callable/Native.h" #include "gse/Exception.h" #include "game/unit/Unit.h" +#include "game/base/Base.h" namespace game { namespace map { @@ -219,6 +221,18 @@ WRAPIMPL_BEGIN( Tile, CLASS_TILE ) return VALUE( gse::type::Array, result ); } ) }, + { + "get_base", + NATIVE_CALL( this ) { + N_EXPECT_ARGS( 0 ); + if ( base ) { + return base->Wrap(); + } + else { + return VALUE( gse::type::Null ); + } + } ) + }, }; WRAPIMPL_END_PTR( Tile ) diff --git a/src/game/rules/Faction.cpp b/src/game/rules/Faction.cpp index 867b8ce8..7b876b9f 100644 --- a/src/game/rules/Faction.cpp +++ b/src/game/rules/Faction.cpp @@ -9,9 +9,6 @@ namespace game { namespace rules { -const Faction::faction_flag_t Faction::FF_NONE = 0; -const Faction::faction_flag_t Faction::FF_PROGENITOR = 1 << 0; - Faction::Faction() { // } @@ -29,6 +26,7 @@ const types::Buffer Faction::Serialize() const { buf.WriteString( m_name ); buf.WriteColor( m_colors.text ); + buf.WriteColor( m_colors.text_shadow ); buf.WriteColor( m_colors.border ); buf.WriteString( m_bases_render.file ); @@ -51,6 +49,7 @@ void Faction::Unserialize( types::Buffer buf ) { m_name = buf.ReadString(); m_colors.text = buf.ReadColor(); + m_colors.text_shadow = buf.ReadColor(); m_colors.border = buf.ReadColor(); m_bases_render.file = buf.ReadString(); diff --git a/src/game/rules/Faction.h b/src/game/rules/Faction.h index d6950c6a..dfdea4d4 100644 --- a/src/game/rules/Faction.h +++ b/src/game/rules/Faction.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "types/Serializable.h" @@ -17,8 +18,9 @@ CLASS2( Faction, types::Serializable, gse::Wrappable ) Faction( const std::string& id, const std::string& name ); typedef uint8_t faction_flag_t; - static const faction_flag_t FF_NONE; - static const faction_flag_t FF_PROGENITOR; + static constexpr Faction::faction_flag_t FF_NONE = 0; + static constexpr Faction::faction_flag_t FF_PROGENITOR = 1 << 0; + static constexpr Faction::faction_flag_t FF_NAVAL = 1 << 1; std::string m_id = ""; std::string m_name = ""; @@ -26,9 +28,15 @@ CLASS2( Faction, types::Serializable, gse::Wrappable ) struct { types::Color text = {}; + types::Color text_shadow = {}; types::Color border = {}; } m_colors = {}; + struct { + std::vector< std::string > land = {}; + std::vector< std::string > water = {}; + } m_base_names = {}; + bases_render_info_t m_bases_render = {}; const types::Buffer Serialize() const override; diff --git a/src/gse/Value.cpp b/src/gse/Value.cpp index 532893bc..521b590b 100644 --- a/src/gse/Value.cpp +++ b/src/gse/Value.cpp @@ -104,6 +104,8 @@ const Value Value::New( const type::Type* type ) const { } case type::Type::T_RANGE: THROW( "ranges are not supposed to be cloned" ); + case type::Type::T_LOOPCONTROL: + THROW( "loop controls are not supposed to be cloned" ); default: { THROW( "unsupported value type: " + ToString() ); } diff --git a/src/gse/builtins/Builtins.cpp b/src/gse/builtins/Builtins.cpp index 06335ee6..55ac79a2 100644 --- a/src/gse/builtins/Builtins.cpp +++ b/src/gse/builtins/Builtins.cpp @@ -9,6 +9,7 @@ void Builtins::AddToContext( context::Context* ctx ) { m_console.AddToContext( ctx ); m_conversions.AddToContext( ctx ); m_math.AddToContext( ctx ); + m_string.AddToContext( ctx ); } } diff --git a/src/gse/builtins/Builtins.h b/src/gse/builtins/Builtins.h index 6cb78fe5..e087ee4e 100644 --- a/src/gse/builtins/Builtins.h +++ b/src/gse/builtins/Builtins.h @@ -8,6 +8,7 @@ #include "Include.h" #include "Conversions.h" #include "Math.h" +#include "String.h" namespace gse { namespace builtins { @@ -27,6 +28,7 @@ class Builtins : public Bindings { Console m_console = {}; Conversions m_conversions = {}; Math m_math = {}; + String m_string = {}; }; diff --git a/src/gse/builtins/CMakeLists.txt b/src/gse/builtins/CMakeLists.txt index 949ad7a6..e018a4e8 100644 --- a/src/gse/builtins/CMakeLists.txt +++ b/src/gse/builtins/CMakeLists.txt @@ -6,5 +6,6 @@ SET( SRC ${SRC} ${PWD}/Console.cpp ${PWD}/Conversions.cpp ${PWD}/Math.cpp + ${PWD}/String.cpp PARENT_SCOPE ) diff --git a/src/gse/builtins/Math.cpp b/src/gse/builtins/Math.cpp index e2344cde..782875bd 100644 --- a/src/gse/builtins/Math.cpp +++ b/src/gse/builtins/Math.cpp @@ -42,11 +42,17 @@ void Math::AddToContext( context::Context* ctx ) { AB( "max", std::max, std::fmax ) #undef AB - ctx->CreateBuiltin( "round", NATIVE_CALL() { - N_EXPECT_ARGS( 1 ); - N_GETVALUE( v, 0, Float ); - return VALUE( type::Int, std::round( v ) ); +#define F( _func, _in_type, _out_type ) \ + ctx->CreateBuiltin( #_func, NATIVE_CALL() { \ + N_EXPECT_ARGS( 1 ); \ + N_GETVALUE( v, 0, _in_type ); \ + return VALUE( type::_out_type, std::_func( v ) ); \ } ) ); + F( floor, Float, Int ) + F( round, Float, Int ) + F( ceil, Float, Int ) + F( sqrt, Float, Float ) +#undef F } diff --git a/src/gse/builtins/String.cpp b/src/gse/builtins/String.cpp new file mode 100644 index 00000000..50586ece --- /dev/null +++ b/src/gse/builtins/String.cpp @@ -0,0 +1,33 @@ +#include "String.h" + +#include + +#include "gse/context/Context.h" +#include "gse/callable/Native.h" +#include "gse/Exception.h" +#include "gse/type/String.h" +#include "gse/type/Undefined.h" + +namespace gse { +namespace builtins { + +void String::AddToContext( context::Context* ctx ) { + + ctx->CreateBuiltin( "to_uppercase", NATIVE_CALL() { + N_EXPECT_ARGS( 1 ); + N_GETVALUE_NONCONST( text, 0, String ); + std::transform( text.begin(), text.end(), text.begin(), ::toupper ); + return VALUE( type::String, text ); + } ) ); + + ctx->CreateBuiltin( "to_lowercase", NATIVE_CALL() { + N_EXPECT_ARGS( 1 ); + N_GETVALUE_NONCONST( text, 0, String ); + std::transform( text.begin(), text.end(), text.begin(), ::tolower ); + return VALUE( type::String, text ); + } ) ); + +} + +} +} diff --git a/src/gse/builtins/String.h b/src/gse/builtins/String.h new file mode 100644 index 00000000..7314dbe1 --- /dev/null +++ b/src/gse/builtins/String.h @@ -0,0 +1,14 @@ +#pragma once + +#include "gse/Bindings.h" + +namespace gse { +namespace builtins { + +class String : public Bindings { +public: + void AddToContext( context::Context* ctx ) override; +}; + +} +} diff --git a/src/gse/callable/Native.h b/src/gse/callable/Native.h index fccab14c..1c8ec497 100644 --- a/src/gse/callable/Native.h +++ b/src/gse/callable/Native.h @@ -50,11 +50,18 @@ namespace callable { const auto& _var = (gse::type::Callable*)arg; #define N_UNPERSIST_CALLABLE( _callable ) \ ctx->UnpersistValue( _callable ); -#define N_GETVALUE( _var, _index, _type ) \ +#define N_GETVALUE_NONCONST( _var, _index, _type ) \ ASSERT_NOLOG( _index < arguments.size(), "argument index overflow" ); \ arg = arguments.at( _index ).Get(); \ N_CHECKARG( arg, _index, _type ); \ + auto _var = ((gse::type::_type*)arg)->value; +#define N_GETELEMENT( _var, _arr, _index, _type ) \ + ASSERT_NOLOG( _index < _arr.size(), #_arr " index overflow" ); \ + arg = _arr.at( _index ).Get(); \ + N_CHECKARG( arg, _index, _type ); \ const auto& _var = ((gse::type::_type*)arg)->value; +#define N_GETVALUE( _var, _index, _type ) \ + N_GETELEMENT( _var, arguments, _index, _type ); #define N_GETOBJ( _value, _class ) \ arg = _value.Get()->Deref(); \ N_CHECKTYPE( arg, Object ); \ diff --git a/src/gse/context/Context.cpp b/src/gse/context/Context.cpp index 50f139b2..aa62bc26 100644 --- a/src/gse/context/Context.cpp +++ b/src/gse/context/Context.cpp @@ -93,6 +93,15 @@ void Context::UpdateVariable( const std::string& name, const Value& value, const throw Exception( EC.REFERENCE_ERROR, "Variable '" + name + "' is not defined", this, *si ); } +void Context::DestroyVariable( const std::string& name, const si_t* si ) { + const auto it = m_variables.find( name ); + if ( it != m_variables.end() ) { + m_variables.erase( it ); + return; + } + throw Exception( EC.REFERENCE_ERROR, "Variable '" + name + "' is not defined", this, *si ); +} + void Context::CreateBuiltin( const std::string& name, const Value& value ) { CreateConst( "#" + name, value, nullptr ); } diff --git a/src/gse/context/Context.h b/src/gse/context/Context.h index e9c88219..a8c60d21 100644 --- a/src/gse/context/Context.h +++ b/src/gse/context/Context.h @@ -45,6 +45,7 @@ class Context { void CreateVariable( const std::string& name, const Value& value, const si_t* si ); void CreateConst( const std::string& name, const Value& value, const si_t* si ); void UpdateVariable( const std::string& name, const Value& value, const si_t* si ); + void DestroyVariable( const std::string& name, const si_t* si ); void CreateBuiltin( const std::string& name, const Value& value ); void PersistValue( const Value& value ); void UnpersistValue( const Value& value ); diff --git a/src/gse/parser/JS.cpp b/src/gse/parser/JS.cpp index 30aa7baa..ee95ab8d 100644 --- a/src/gse/parser/JS.cpp +++ b/src/gse/parser/JS.cpp @@ -23,7 +23,12 @@ #include "gse/program/ElseIf.h" #include "gse/program/Else.h" #include "gse/program/While.h" +#include "gse/program/For.h" #include "gse/program/Try.h" +#include "gse/program/SimpleCondition.h" +#include "gse/program/ForConditionInOf.h" +#include "gse/program/ForConditionExpressions.h" +#include "gse/program/LoopControl.h" namespace gse { @@ -42,6 +47,7 @@ void JS::GetElements( source_elements_t& elements ) { si_t si; std::string value; std::unordered_map< std::string, Parser::Conditional::conditional_type_t >::const_iterator control_it; + std::unordered_map< std::string, program::loop_control_type_t >::const_iterator loop_control_it; while ( !eof() ) { begin = get_si_pos(); if ( match_sequence( "//", true ) ) { @@ -70,6 +76,10 @@ void JS::GetElements( source_elements_t& elements ) { } else if ( KEYWORDS.find( value ) != KEYWORDS.end() ) { elements.push_back( new Operator( value, si ) ); + loop_control_it = LOOP_CONTROL_KEYWORDS.find( value ); + if ( loop_control_it != LOOP_CONTROL_KEYWORDS.end() ) { + elements.push_back( new LoopControl( loop_control_it->second, si ) ); + } } else { elements.push_back( new Identifier( value, IDENTIFIER_VARIABLE, si ) ); @@ -265,7 +275,7 @@ const program::Conditional* JS::GetConditional( const source_elements_t::const_i ASSERT( ( *begin )->m_type == SourceElement::ET_CONDITIONAL, "conditional expected here" ); Conditional* conditional = (Conditional*)( *begin ); source_elements_t::const_iterator it = begin + 1, it_end; - const program::Expression* condition = nullptr; + const program::Condition* condition = nullptr; if ( conditional->has_condition ) { if ( it == end || @@ -280,20 +290,98 @@ const program::Conditional* JS::GetConditional( const source_elements_t::const_i ); } it_end = GetBracketsEnd( it, end ); - condition = GetExpression( it + 1, it_end ); + if (conditional->m_conditional_type == Conditional::CT_FOR ) { + if (++it == end) { + throw Exception( + EC.PARSE_ERROR, "Expected iteration condition" + ( it == end + ? " here" + : ", got: " + ( *it )->ToString() + ), nullptr, ( *begin )->m_si + ); + } + // check if it's expressions-based loop (and process if it is) + auto it_first = it; + auto it_second = it_end; + while ( it_first != it_end ) { + if ( + (*it_first)->m_type == SourceElement::ET_DELIMITER && + ((Delimiter*)*it_first)->m_delimiter_type == Delimiter::DT_CODE + ) { + // found first ; + it_second = it_first + 1; + while ( it_second != it_end ) { + if ( + (*it_second)->m_type == SourceElement::ET_DELIMITER && + ((Delimiter*)*it_second)->m_delimiter_type == Delimiter::DT_CODE + ) { + // found second ; + break; + } + it_second++; + } + break; + } + it_first++; + } + if ( + it != it_first && + it + 1 != it_first && + it_first != it_end && + it_first + 1 != it_second && + it_second != it_end && + it_second + 1 != it_end + ) { + condition = new program::ForConditionExpressions( + ( *it )->m_si, + GetExpression( it, it_first ), + GetExpression( it_first + 1, it_second ), + GetExpression( it_second + 1, it_end ) ); + } + else { + // in-of loop + const auto* variable = ( Identifier * ) * ( it++ ); + if ( it == end || ( *it )->m_type != SourceElement::ET_IDENTIFIER ) { + throw Exception( EC.PARSE_ERROR, "Expected iteration condition" + ( it == end + ? " here" + : ", got: " + ( *it )->ToString() + ), nullptr, ( *begin )->m_si ); + } + const auto* in_or_of = ( Identifier * ) * ( it++ ); + ForConditionInOf::for_inof_condition_type_t type; + if ( in_or_of->m_name == "in" ) { + type = ForConditionInOf::FIC_IN; + } + else if ( in_or_of->m_name == "of" ) { + type = ForConditionInOf::FIC_OF; + } + else { + throw Exception( EC.PARSE_ERROR, "Expected iteration condition, got: " + in_or_of->ToString(), nullptr, ( *begin )->m_si ); + } + condition = new program::ForConditionInOf( + ( *it )->m_si, + new Variable( variable->m_si, variable->m_name ), + type, + GetExpression( it, it_end ) + ); + } + } + else { + condition = new program::SimpleCondition( ( *it )->m_si, GetExpression( it + 1, it_end ) ); + } ASSERT( it_end != end, "expected {, got EOF" ); it = it_end + 1; } - if ( - it == end || - ( *it )->m_type != SourceElement::ET_BLOCK || - ( ( Block * )( *it ) )->m_block_side != Block::BS_BEGIN || - ( ( Block * )( *it ) )->m_block_type != BLOCK_CURLY_BRACKETS ) { + if ( it == end ) { throw Exception( - EC.PARSE_ERROR, "Expected {" + ( it == end - ? " here" - : ", got: " + ( *it )->ToString() - ), nullptr, ( *begin )->m_si + EC.PARSE_ERROR, "Expected { here", nullptr, ( *begin )->m_si + ); + } + else if ( + ( *it )->m_type != SourceElement::ET_BLOCK || + ( ( Block * )( *it ) )->m_block_side != Block::BS_BEGIN || + ( ( Block * )( *it ) )->m_block_type != BLOCK_CURLY_BRACKETS ) { + throw Exception( + EC.PARSE_ERROR, "Expected {, got: " + ( *it )->ToString(), nullptr, ( *begin )->m_si ); } it_end = GetBracketsEnd( it, end ); @@ -314,16 +402,19 @@ const program::Conditional* JS::GetConditional( const source_elements_t::const_i it++; } if ( conditional->m_conditional_type == Conditional::CT_IF ) { - return new program::If( si, condition, body, els ); + return new program::If( si, (SimpleCondition*)condition, body, els ); } else { - return new program::ElseIf( si, condition, body, els ); + return new program::ElseIf( si, (SimpleCondition*)condition, body, els ); } } case Conditional::CT_ELSE: return new program::Else( GetSI( begin, end ), body ); case Conditional::CT_WHILE: - return new program::While( GetSI( begin, end ), condition, body ); + return new program::While( GetSI( begin, end ), (SimpleCondition*)condition, body ); + case Conditional::CT_FOR: { + return new program::For( GetSI( begin, end ), (ForCondition*)condition, body ); + } case Conditional::CT_TRY: { if ( it == end || @@ -576,6 +667,10 @@ const program::Operand* JS::GetExpressionOrOperand( const source_elements_t::con } else { elements.push_back( GetOperator( (Operator*)( *it ) ) ); + if ( op == "return" && ( ( it + 1 == it_end ) || ( *(it + 1))->m_type == SourceElement::ET_DELIMITER ) ) { + // return undefined by default + elements.push_back( new program::Value( (*it)->m_si, VALUE( type::Undefined ) ) ); + } } var_hints_allowed = false; break; @@ -779,6 +874,10 @@ const program::Operand* JS::GetExpressionOrOperand( const source_elements_t::con it = it_end; break; } + case SourceElement::ET_LOOP_CONTROL: { + elements.push_back( GetLoopControl( (LoopControl*)*it ) ); + break; + } default: throw Exception( EC.PARSE_ERROR, "Unexpected: " + ( *it )->ToString() + "", nullptr, ( *it )->m_si ); } @@ -941,6 +1040,10 @@ const program::Object* JS::GetObject( const source_elements_t::const_iterator& b return new program::Object( GetSI( begin - 1, end + 1 ), ordered_properties ); } +const program::LoopControl* JS::GetLoopControl( const LoopControl* loop_control ) { + return new program::LoopControl( loop_control->m_si, loop_control->m_loop_control_type ); +} + const JS::source_elements_t::const_iterator JS::GetBracketsEnd( const source_elements_t::const_iterator& begin, const source_elements_t::const_iterator& end ) const { ASSERT( ( *begin )->m_type == SourceElement::ET_BLOCK, "expected block" ); std::stack< uint8_t > brackets = {}; diff --git a/src/gse/parser/JS.h b/src/gse/parser/JS.h index 507ae11c..922f5374 100644 --- a/src/gse/parser/JS.h +++ b/src/gse/parser/JS.h @@ -24,6 +24,7 @@ class Operator; class Operand; class Object; class Array; +class LoopControl; } namespace parser { @@ -50,6 +51,7 @@ CLASS( JS, Parser ) const program::Operator* GetOperator( const Operator* element ); const program::Array* GetArray( const source_elements_t::const_iterator& begin, const source_elements_t::const_iterator& end ); const program::Object* GetObject( const source_elements_t::const_iterator& begin, const source_elements_t::const_iterator& end ); + const program::LoopControl* GetLoopControl( const LoopControl* loop_control ); const std::string CHARS_WHITESPACE = CHARS_EOLN + " "; const std::string CHARS_NAMES = CHARS_LETTERS + "_#"; @@ -93,6 +95,8 @@ CLASS( JS, Parser ) const std::unordered_set< std::string > KEYWORDS = { "return", + "break", + "continue", "throw", "let", "const", @@ -119,6 +123,10 @@ CLASS( JS, Parser ) "while", Parser::Conditional::CT_WHILE }, + { + "for", + Parser::Conditional::CT_FOR + }, { "try", Parser::Conditional::CT_TRY @@ -129,6 +137,17 @@ CLASS( JS, Parser ) } }; + const std::unordered_map< std::string, program::loop_control_type_t > LOOP_CONTROL_KEYWORDS = { + { + "break", + program::LCT_BREAK + }, + { + "continue", + program::LCT_CONTINUE + } + }; + const std::unordered_map< std::string, program::variable_hints_t > MODIFIER_OPERATORS = { { { @@ -166,6 +185,14 @@ CLASS( JS, Parser ) "return", program::OT_RETURN }, + { + "break", + program::OT_BREAK + }, + { + "continue", + program::OT_CONTINUE + }, { "throw", program::OT_THROW @@ -292,7 +319,21 @@ CLASS( JS, Parser ) program::OT_RETURN, { 1, - OL_RIGHT + OL_RIGHT // undefined will be auto-appended if missing + } + }, + { + program::OT_BREAK, + { + 1, + OL_RIGHT // loop control will be auto-appended + } + }, + { + program::OT_CONTINUE, + { + 1, + OL_RIGHT // loop control will be auto-appended } }, { diff --git a/src/gse/parser/Parser.cpp b/src/gse/parser/Parser.cpp index 8c714aea..8431d500 100644 --- a/src/gse/parser/Parser.cpp +++ b/src/gse/parser/Parser.cpp @@ -6,7 +6,7 @@ namespace gse { namespace parser { Parser::Parser( const std::string& filename, const std::string& source, const size_t initial_line_num ) - : m_source( source ) + : m_source( source + '\n' ) , m_filename( filename ) , m_begin( m_source.c_str() ) , m_end( m_source.c_str() + m_source.size() ) { diff --git a/src/gse/parser/Parser.h b/src/gse/parser/Parser.h index 4d7cb07a..96409686 100644 --- a/src/gse/parser/Parser.h +++ b/src/gse/parser/Parser.h @@ -6,6 +6,7 @@ #include "common/Common.h" #include "gse/Types.h" +#include "gse/program/Types.h" namespace gse { @@ -40,6 +41,7 @@ CLASS( Parser, common::Class ) ET_DELIMITER, ET_CONDITIONAL, ET_BLOCK, + ET_LOOP_CONTROL }; SourceElement( const element_type_t type, const si_t& si ) : m_type( type ) @@ -123,6 +125,7 @@ CLASS( Parser, common::Class ) CT_ELSEIF, CT_ELSE, CT_WHILE, + CT_FOR, CT_TRY, CT_CATCH, }; @@ -132,7 +135,8 @@ CLASS( Parser, common::Class ) , has_condition( conditional_type == CT_IF || conditional_type == CT_ELSEIF || - conditional_type == CT_WHILE + conditional_type == CT_WHILE || + conditional_type == CT_FOR ) {}; const conditional_type_t m_conditional_type; @@ -148,6 +152,8 @@ CLASS( Parser, common::Class ) return "else"; case CT_WHILE: return "while"; + case CT_FOR: + return "for"; case CT_TRY: return "try"; case CT_CATCH: @@ -201,6 +207,26 @@ CLASS( Parser, common::Class ) } } }; + class LoopControl : public SourceElement { + public: + LoopControl( const program::loop_control_type_t loop_control_type, const si_t& si ) + : SourceElement( ET_LOOP_CONTROL, si ) + , m_loop_control_type( loop_control_type ) {} + const program::loop_control_type_t m_loop_control_type; + const std::string ToString() const override { + switch ( m_loop_control_type ) { + case program::LCT_BREAK: + return "break"; + case program::LCT_CONTINUE: + return "continue"; + default: + THROW( "unexpected loop control type: " + std::to_string( m_loop_control_type ) ); + } + } + const std::string Dump() const override { + return "loop_control{" + ToString() + "}"; + } + }; virtual void GetElements( source_elements_t& elements ) = 0; virtual const program::Program* GetProgram( const source_elements_t& elements ) = 0; diff --git a/src/gse/program/CMakeLists.txt b/src/gse/program/CMakeLists.txt index 6972a63a..b4b2da8e 100644 --- a/src/gse/program/CMakeLists.txt +++ b/src/gse/program/CMakeLists.txt @@ -4,6 +4,12 @@ SET( SRC ${SRC} ${PWD}/Call.cpp ${PWD}/Catch.cpp ${PWD}/Conditional.cpp + ${PWD}/Condition.cpp + ${PWD}/SimpleCondition.cpp + ${PWD}/ForCondition.cpp + ${PWD}/ForConditionInOf.cpp + ${PWD}/ForConditionExpressions.cpp + ${PWD}/LoopControl.cpp ${PWD}/Control.cpp ${PWD}/Element.cpp ${PWD}/Else.cpp @@ -22,6 +28,7 @@ SET( SRC ${SRC} ${PWD}/Value.cpp ${PWD}/Variable.cpp ${PWD}/While.cpp + ${PWD}/For.cpp ) diff --git a/src/gse/program/Condition.cpp b/src/gse/program/Condition.cpp new file mode 100644 index 00000000..b2fbee44 --- /dev/null +++ b/src/gse/program/Condition.cpp @@ -0,0 +1,13 @@ +#include "Condition.h" + +#include "Expression.h" + +namespace gse { +namespace program { + +Condition::Condition( const si_t& si, const condition_type_t type ) + : Element( si, ET_CONDITION ) + , type( type ) {} + +} +} diff --git a/src/gse/program/Condition.h b/src/gse/program/Condition.h new file mode 100644 index 00000000..60fe98ce --- /dev/null +++ b/src/gse/program/Condition.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Operand.h" + +namespace gse { +namespace program { + +class Expression; + +class Condition : public Element { +public: + enum condition_type_t { + CT_NOTHING, + CT_SIMPLE, + }; + + Condition( const si_t& si, const condition_type_t type ); + virtual ~Condition() = default; + + const condition_type_t type; + +}; + +} +} diff --git a/src/gse/program/Conditional.h b/src/gse/program/Conditional.h index eb97b2bd..3de1d5ba 100644 --- a/src/gse/program/Conditional.h +++ b/src/gse/program/Conditional.h @@ -12,6 +12,7 @@ class Conditional : public Control { CT_ELSEIF, CT_ELSE, CT_WHILE, + CT_FOR, CT_TRY, CT_CATCH, }; diff --git a/src/gse/program/Element.h b/src/gse/program/Element.h index 5195e398..021c411f 100644 --- a/src/gse/program/Element.h +++ b/src/gse/program/Element.h @@ -11,6 +11,7 @@ class Element { ET_OPERAND, ET_OPERATOR, ET_CONDITIONAL, + ET_CONDITION, }; Element( const si_t& si, const element_type_t element_type ); virtual ~Element() = default; diff --git a/src/gse/program/ElseIf.cpp b/src/gse/program/ElseIf.cpp index 0d3d1446..ffe3bbc4 100644 --- a/src/gse/program/ElseIf.cpp +++ b/src/gse/program/ElseIf.cpp @@ -1,12 +1,12 @@ #include "ElseIf.h" -#include "Expression.h" +#include "SimpleCondition.h" #include "Scope.h" namespace gse { namespace program { -ElseIf::ElseIf( const si_t& si, const Expression* condition, const Scope* body, const Conditional* els ) +ElseIf::ElseIf( const si_t& si, const SimpleCondition* condition, const Scope* body, const Conditional* els ) : Conditional( si, CT_ELSEIF ) , condition( condition ) , body( body ) diff --git a/src/gse/program/ElseIf.h b/src/gse/program/ElseIf.h index 4d2427b9..37ef9a14 100644 --- a/src/gse/program/ElseIf.h +++ b/src/gse/program/ElseIf.h @@ -5,16 +5,16 @@ namespace gse { namespace program { -class Expression; +class SimpleCondition; class Scope; class ElseIf : public Conditional { public: - ElseIf( const si_t& si, const Expression* condition, const Scope* body, const Conditional* els = nullptr ); + ElseIf( const si_t& si, const SimpleCondition* condition, const Scope* body, const Conditional* els = nullptr ); ~ElseIf(); - const Expression* condition; + const SimpleCondition* condition; const Scope* body; const Conditional* els; diff --git a/src/gse/program/For.cpp b/src/gse/program/For.cpp new file mode 100644 index 00000000..c294917d --- /dev/null +++ b/src/gse/program/For.cpp @@ -0,0 +1,31 @@ +#include "For.h" + +#include "ForCondition.h" +#include "Scope.h" + +namespace gse { +namespace program { + +For::For( const si_t& si, const ForCondition* condition, const Scope* body ) + : Conditional( si, CT_FOR ) + , condition( condition ) + , body( body ) {} + +For::~For() { + delete condition; + delete body; +} + +const std::string For::Dump( const size_t depth ) const { + return Formatted( "For" + m_si.ToString() + "(", depth ) + + Formatted( "Condition(", depth + 1 ) + + condition->Dump( depth + 2 ) + + Formatted( ")", depth + 1 ) + + Formatted( "Body(", depth + 1 ) + + body->Dump( depth + 2 ) + + Formatted( ")", depth + 1 ) + + Formatted( ")", depth ); +} + +} +} diff --git a/src/gse/program/For.h b/src/gse/program/For.h new file mode 100644 index 00000000..a1840f58 --- /dev/null +++ b/src/gse/program/For.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Conditional.h" + +namespace gse { +namespace program { + +class ForCondition; +class Scope; + +class For : public Conditional { +public: + + For( const si_t& si, const ForCondition* condition, const Scope* body ); + ~For(); + + const ForCondition* condition; + const Scope* body; + + const std::string Dump( const size_t depth = 0 ) const override; +}; + +} +} diff --git a/src/gse/program/ForCondition.cpp b/src/gse/program/ForCondition.cpp new file mode 100644 index 00000000..052ab4d3 --- /dev/null +++ b/src/gse/program/ForCondition.cpp @@ -0,0 +1,11 @@ +#include "ForCondition.h" + +namespace gse { +namespace program { + +ForCondition::ForCondition( const si_t& si, const for_condition_type_t type ) + : Condition( si, CT_SIMPLE ) + , for_type( type ) {} + +} +} diff --git a/src/gse/program/ForCondition.h b/src/gse/program/ForCondition.h new file mode 100644 index 00000000..55151ac9 --- /dev/null +++ b/src/gse/program/ForCondition.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Condition.h" + +namespace gse { +namespace program { + +class ForCondition : public Condition { +public: + enum for_condition_type_t { + FCT_NOTHING, + FCT_IN_OF, + FCT_EXPRESSIONS, + }; + + ForCondition( const si_t& si, const for_condition_type_t type ); + virtual ~ForCondition() = default; + + const for_condition_type_t for_type; + + virtual const std::string ToString() const = 0; + virtual const std::string Dump( const size_t depth = 0 ) const = 0; +}; + +} +} diff --git a/src/gse/program/ForConditionExpressions.cpp b/src/gse/program/ForConditionExpressions.cpp new file mode 100644 index 00000000..0d691b66 --- /dev/null +++ b/src/gse/program/ForConditionExpressions.cpp @@ -0,0 +1,43 @@ +#include "ForConditionExpressions.h" + +#include "Variable.h" +#include "Expression.h" + +namespace gse { +namespace program { + +ForConditionExpressions::ForConditionExpressions( const si_t& si, const Expression* init, const Expression* check, const Expression* iterate ) + : ForCondition( si, FCT_EXPRESSIONS ) + , init( init ) + , check( check ) + , iterate( iterate ) {} + +ForConditionExpressions::~ForConditionExpressions() { + if ( init ) { + delete init; + } + if ( iterate ) { + delete iterate; + } + if ( check ) { + delete check; + } +} + +const std::string ForConditionExpressions::ToString() const { + return init->ToString() + " ; " + iterate->ToString() + " ; " + check->ToString(); +} + +const std::string ForConditionExpressions::Dump( const size_t depth ) const { + return + Formatted( "ForCondition" + m_si.ToString() + "(", depth ) + + init->Dump( depth + 1 ) + + Formatted( ";", depth + 1 ) + + iterate->Dump( depth + 1 ) + + Formatted( ";", depth + 1 ) + + check->Dump( depth + 1 ) + + Formatted( ")", depth ); +} + +} +} diff --git a/src/gse/program/ForConditionExpressions.h b/src/gse/program/ForConditionExpressions.h new file mode 100644 index 00000000..30ddfa8e --- /dev/null +++ b/src/gse/program/ForConditionExpressions.h @@ -0,0 +1,24 @@ +#pragma once + +#include "ForCondition.h" + +namespace gse { +namespace program { + +class Expression; + +class ForConditionExpressions : public ForCondition { +public: + ForConditionExpressions( const si_t& si, const Expression* init, const Expression* check, const Expression* iterate ); + ~ForConditionExpressions(); + + const Expression* init; + const Expression* check; + const Expression* iterate; + + const std::string ToString() const override; + const std::string Dump( const size_t depth = 0 ) const override; +}; + +} +} diff --git a/src/gse/program/ForConditionInOf.cpp b/src/gse/program/ForConditionInOf.cpp new file mode 100644 index 00000000..43564264 --- /dev/null +++ b/src/gse/program/ForConditionInOf.cpp @@ -0,0 +1,47 @@ +#include "ForConditionInOf.h" + +#include "Variable.h" +#include "Expression.h" + +namespace gse { +namespace program { + +ForConditionInOf::ForConditionInOf( const si_t& si, const Variable* variable, const for_inof_condition_type_t type, const Expression* expression ) + : ForCondition( si, FCT_IN_OF ) + , variable( variable ) + , for_inof_type( type ) + , expression( expression ) {} + +ForConditionInOf::~ForConditionInOf() { + if ( variable ) { + delete variable; + } + if ( expression ) { + delete expression; + } +} + +const std::string ForConditionInOf::ToString() const { + return variable->ToString() + ( for_inof_type == FIC_IN + ? " in " + : " of " + ) + expression->ToString(); +} + +const std::string ForConditionInOf::Dump( const size_t depth ) const { + return + Formatted( "ForCondition" + m_si.ToString() + "(", depth ) + + variable->Dump( depth + 1 ) + + Formatted( + for_inof_type == FIC_IN + ? " in " + : " of ", + depth + 1 + ) + + expression->Dump( depth + 1 ) + + + Formatted( ")", depth ); +} + +} +} diff --git a/src/gse/program/ForConditionInOf.h b/src/gse/program/ForConditionInOf.h new file mode 100644 index 00000000..e2ea387f --- /dev/null +++ b/src/gse/program/ForConditionInOf.h @@ -0,0 +1,31 @@ +#pragma once + +#include "ForCondition.h" + +namespace gse { +namespace program { + +class Variable; +class Expression; + +class ForConditionInOf : public ForCondition { +public: + enum for_inof_condition_type_t { + FIC_NOTHING, + FIC_IN, + FIC_OF, + }; + + ForConditionInOf( const si_t& si, const Variable* variable, const for_inof_condition_type_t type, const Expression* expression ); + ~ForConditionInOf(); + + const Variable* variable; + const for_inof_condition_type_t for_inof_type; + const Expression* expression; + + const std::string ToString() const override; + const std::string Dump( const size_t depth = 0 ) const override; +}; + +} +} diff --git a/src/gse/program/If.cpp b/src/gse/program/If.cpp index ceae925e..fd606b51 100644 --- a/src/gse/program/If.cpp +++ b/src/gse/program/If.cpp @@ -1,12 +1,12 @@ #include "If.h" -#include "Expression.h" +#include "SimpleCondition.h" #include "Scope.h" namespace gse { namespace program { -If::If( const si_t& si, const Expression* condition, const Scope* body, const Conditional* els ) +If::If( const si_t& si, const SimpleCondition* condition, const Scope* body, const Conditional* els ) : Conditional( si, CT_IF ) , condition( condition ) , body( body ) diff --git a/src/gse/program/If.h b/src/gse/program/If.h index 4379ccd9..45c50d64 100644 --- a/src/gse/program/If.h +++ b/src/gse/program/If.h @@ -5,16 +5,16 @@ namespace gse { namespace program { -class Expression; +class SimpleCondition; class Scope; class If : public Conditional { public: - If( const si_t& si, const Expression* condition, const Scope* body, const Conditional* els = nullptr ); + If( const si_t& si, const SimpleCondition* condition, const Scope* body, const Conditional* els = nullptr ); ~If(); - const Expression* condition; + const SimpleCondition* condition; const Scope* body; const Conditional* els; diff --git a/src/gse/program/LoopControl.cpp b/src/gse/program/LoopControl.cpp new file mode 100644 index 00000000..a47b887c --- /dev/null +++ b/src/gse/program/LoopControl.cpp @@ -0,0 +1,27 @@ +#include "LoopControl.h" + +#include "common/Assert.h" + +namespace gse { +namespace program { + +LoopControl::LoopControl( const si_t& si, const loop_control_type_t loop_control_type ) + : Operand( si, OT_LOOP_CONTROL ) + , loop_control_type( loop_control_type ) {} + +const std::string LoopControl::ToString() const { + switch ( loop_control_type ) { + case LCT_BREAK: + return "break"; + case LCT_CONTINUE: + return "continue"; + default: + THROW( "unexpected control type: " + std::to_string( loop_control_type ) ); + } +} +const std::string LoopControl::Dump( const size_t depth ) const { + return Formatted( "LoopControl" + m_si.ToString() + "( " + ToString() + " )", depth ); +} + +} +} diff --git a/src/gse/program/LoopControl.h b/src/gse/program/LoopControl.h new file mode 100644 index 00000000..cb46b060 --- /dev/null +++ b/src/gse/program/LoopControl.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "Operand.h" + +#include "Types.h" + +namespace gse { +namespace program { + +class LoopControl : public Operand { +public: + + LoopControl( const si_t& si, const loop_control_type_t loop_control_type ); + + const loop_control_type_t loop_control_type; + + const std::string ToString() const override; + const std::string Dump( const size_t depth = 0 ) const override; +}; + +} +} diff --git a/src/gse/program/Operand.h b/src/gse/program/Operand.h index 243a6af6..974ee924 100644 --- a/src/gse/program/Operand.h +++ b/src/gse/program/Operand.h @@ -17,6 +17,7 @@ class Operand : public Element { OT_EXPRESSION, OT_FUNCTION, OT_CALL, + OT_LOOP_CONTROL, }; Operand( const si_t& si, const operand_type_t type ); diff --git a/src/gse/program/Operator.cpp b/src/gse/program/Operator.cpp index d46c769d..42a403bc 100644 --- a/src/gse/program/Operator.cpp +++ b/src/gse/program/Operator.cpp @@ -14,6 +14,14 @@ static const std::unordered_map< operator_type_t, std::string > s_op_labels = { OT_RETURN, "return" }, + { + OT_BREAK, + "break" + }, + { + OT_CONTINUE, + "continue" + }, { OT_THROW, "throw" diff --git a/src/gse/program/SimpleCondition.cpp b/src/gse/program/SimpleCondition.cpp new file mode 100644 index 00000000..b0fa1d88 --- /dev/null +++ b/src/gse/program/SimpleCondition.cpp @@ -0,0 +1,37 @@ +#include "SimpleCondition.h" + +#include "Expression.h" + +namespace gse { +namespace program { + +SimpleCondition::SimpleCondition( const si_t& si, const Expression* expression ) + : Condition( si, CT_SIMPLE ) + , expression( expression ) {} + +SimpleCondition::~SimpleCondition() { + if ( expression ) { + delete expression; + } +} + +const std::string SimpleCondition::ToString() const { + std::string result = ""; + if ( expression ) { + result += expression->ToString(); + } + return result; +} + +const std::string SimpleCondition::Dump( const size_t depth ) const { + return + Formatted( "SimpleCondition" + m_si.ToString() + "(", depth ) + + ( expression == nullptr + ? "" + : expression->Dump( depth + 1 ) + ) + + Formatted( ")", depth ); +} + +} +} diff --git a/src/gse/program/SimpleCondition.h b/src/gse/program/SimpleCondition.h new file mode 100644 index 00000000..56d71a1a --- /dev/null +++ b/src/gse/program/SimpleCondition.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Condition.h" + +namespace gse { +namespace program { + +class Expression; + +class SimpleCondition : public Condition { +public: + SimpleCondition( const si_t& si, const Expression* expression ); + ~SimpleCondition(); + + const Expression* expression; + + const std::string ToString() const override; + const std::string Dump( const size_t depth = 0 ) const override; +}; + +} +} diff --git a/src/gse/program/Types.h b/src/gse/program/Types.h index 51e422c4..8657225e 100644 --- a/src/gse/program/Types.h +++ b/src/gse/program/Types.h @@ -14,6 +14,8 @@ enum variable_hints_t : uint8_t { enum operator_type_t { OT_NOOP, OT_RETURN, + OT_BREAK, + OT_CONTINUE, OT_THROW, OT_ASSIGN, OT_NOT, @@ -43,5 +45,11 @@ enum operator_type_t { OT_RANGE, }; +enum loop_control_type_t { + LCT_NONE, + LCT_BREAK, + LCT_CONTINUE, +}; + } } diff --git a/src/gse/program/While.cpp b/src/gse/program/While.cpp index 00fdefbd..94f601df 100644 --- a/src/gse/program/While.cpp +++ b/src/gse/program/While.cpp @@ -1,12 +1,12 @@ #include "While.h" -#include "Expression.h" +#include "SimpleCondition.h" #include "Scope.h" namespace gse { namespace program { -While::While( const si_t& si, const Expression* condition, const Scope* body ) +While::While( const si_t& si, const SimpleCondition* condition, const Scope* body ) : Conditional( si, CT_WHILE ) , condition( condition ) , body( body ) {} diff --git a/src/gse/program/While.h b/src/gse/program/While.h index 8cd11458..d946b184 100644 --- a/src/gse/program/While.h +++ b/src/gse/program/While.h @@ -5,16 +5,16 @@ namespace gse { namespace program { -class Expression; +class SimpleCondition; class Scope; class While : public Conditional { public: - While( const si_t& si, const Expression* condition, const Scope* body ); + While( const si_t& si, const SimpleCondition* condition, const Scope* body ); ~While(); - const Expression* condition; + const SimpleCondition* condition; const Scope* body; const std::string Dump( const size_t depth = 0 ) const override; diff --git a/src/gse/runner/Interpreter.cpp b/src/gse/runner/Interpreter.cpp index ecab756d..30b57ae4 100644 --- a/src/gse/runner/Interpreter.cpp +++ b/src/gse/runner/Interpreter.cpp @@ -6,6 +6,10 @@ #include "gse/program/Object.h" #include "gse/program/Value.h" #include "gse/program/Expression.h" +#include "gse/program/SimpleCondition.h" +#include "gse/program/ForCondition.h" +#include "gse/program/ForConditionInOf.h" +#include "gse/program/ForConditionExpressions.h" #include "gse/program/Operator.h" #include "gse/program/Operand.h" #include "gse/program/Variable.h" @@ -17,10 +21,13 @@ #include "gse/program/ElseIf.h" #include "gse/program/Else.h" #include "gse/program/While.h" +#include "gse/program/For.h" #include "gse/program/Try.h" #include "gse/program/Catch.h" +#include "gse/program/LoopControl.h" #include "gse/type/Type.h" #include "gse/type/Undefined.h" +#include "gse/type/Nothing.h" #include "gse/type/Bool.h" #include "gse/type/Int.h" #include "gse/type/Float.h" @@ -32,6 +39,7 @@ #include "gse/type/ObjectRef.h" #include "gse/type/Callable.h" #include "gse/type/Range.h" +#include "gse/type/LoopControl.h" #include "gse/type/Exception.h" #include "gse/type/Undefined.h" @@ -51,7 +59,7 @@ const gse::Value Interpreter::EvaluateScope( context::Context* ctx, const Scope* const auto subctx = ctx->ForkContext( ctx, scope->m_si, false ); subctx->IncRefs(); - gse::Value result = VALUE( Undefined ); + gse::Value result = VALUE( Nothing ); for ( const auto& it : scope->body ) { switch ( it->control_type ) { case Control::CT_STATEMENT: { @@ -65,7 +73,7 @@ const gse::Value Interpreter::EvaluateScope( context::Context* ctx, const Scope* default: THROW( "unexpected control type: " + it->Dump() ); } - if ( result.Get()->type != Type::T_UNDEFINED ) { + if ( result.Get()->type != Type::T_NOTHING ) { // got return statement break; } @@ -87,21 +95,21 @@ const gse::Value Interpreter::EvaluateStatement( context::Context* ctx, const St if ( returnflag ) { return result; } - return VALUE( Undefined ); + return VALUE( Nothing ); } const gse::Value Interpreter::EvaluateConditional( context::Context* ctx, const Conditional* conditional, bool is_nested ) const { switch ( conditional->conditional_type ) { case Conditional::CT_IF: { const auto* c = (If*)conditional; - if ( EvaluateBool( ctx, c->condition ) ) { + if ( EvaluateBool( ctx, c->condition->expression ) ) { return EvaluateScope( ctx, c->body ); } else if ( c->els ) { return EvaluateConditional( ctx, c->els, true ); } else { - return VALUE( Undefined ); + return VALUE( Nothing ); } } case Conditional::CT_ELSEIF: { @@ -110,14 +118,14 @@ const gse::Value Interpreter::EvaluateConditional( context::Context* ctx, const throw gse::Exception( EC.PARSE_ERROR, "Unexpected elseif without if", ctx, conditional->m_si ); } const auto* c = (ElseIf*)conditional; - if ( EvaluateBool( ctx, c->condition ) ) { + if ( EvaluateBool( ctx, c->condition->expression ) ) { return EvaluateScope( ctx, c->body ); } else if ( c->els ) { return EvaluateConditional( ctx, c->els, true ); } else { - return VALUE( Undefined ); + return VALUE( Nothing ); } } case Conditional::CT_ELSE: { @@ -130,12 +138,117 @@ const gse::Value Interpreter::EvaluateConditional( context::Context* ctx, const } case Conditional::CT_WHILE: { const auto* c = (While*)conditional; - gse::Value result = VALUE( Undefined ); - while ( EvaluateBool( ctx, c->condition ) ) { + gse::Value result = VALUE( Nothing ); + bool need_break = false; + bool need_clear = false; + while ( EvaluateBool( ctx, c->condition->expression ) ) { result = EvaluateScope( ctx, c->body ); - if ( result.Get()->type != Type::T_UNDEFINED ) { + CheckBreakCondition( result, &need_break, &need_clear ); + if ( need_clear ) { + result = VALUE( Nothing ); + } + if ( need_break ) { + break; + } + } + return result; + } + case Conditional::CT_FOR: { + const auto* c = (For*)conditional; + gse::Value result = VALUE( Nothing ); + bool need_break = false; + bool need_clear = false; + switch ( c->condition->for_type ) { + case ForCondition::FCT_EXPRESSIONS: { + const auto* condition = (ForConditionExpressions*)c->condition; + EvaluateExpression( ctx, condition->init ); + while ( EvaluateBool( ctx, condition->check ) ) { + result = EvaluateScope( ctx, c->body ); + CheckBreakCondition( result, &need_break, &need_clear ); + if ( need_break ) { + if ( need_clear ) { + result = VALUE( Nothing ); + } + break; + } + EvaluateExpression( ctx, condition->iterate ); + } + break; + } + case ForCondition::FCT_IN_OF: { + const auto* condition = (ForConditionInOf*)c->condition; + const auto target = EvaluateExpression( ctx, condition->expression ); + const auto forctx = ctx->ForkContext( ctx, condition->m_si, false ); + forctx->IncRefs(); + switch ( target.Get()->type ) { + case Type::T_ARRAY: { + const auto* arr = (type::Array*)target.Get(); + switch ( condition->for_inof_type ) { + case ForConditionInOf::FIC_IN: { + for ( size_t i = 0 ; i < arr->value.size() ; i++ ) { + forctx->CreateConst( condition->variable->name, VALUE( Int, i ), &condition->m_si ); + result = EvaluateScope( forctx, c->body ); + forctx->DestroyVariable( condition->variable->name, &condition->m_si ); + CheckBreakCondition( result, &need_break, &need_clear ); + if ( need_break ) { + if ( need_clear ) { + result = VALUE( Nothing ); + } + break; + } + } + break; + } + case ForConditionInOf::FIC_OF: { + for ( const auto& v : arr->value ) { + forctx->CreateConst( condition->variable->name, v, &condition->m_si ); + result = EvaluateScope( forctx, c->body ); + forctx->DestroyVariable( condition->variable->name, &condition->m_si ); + CheckBreakCondition( result, &need_break, &need_clear ); + if ( need_break ) { + if ( need_clear ) { + result = VALUE( Nothing ); + } + break; + } + } + break; + } + default: + THROW( "unexpected for in_of condition type: " + std::to_string( condition->for_inof_type ) ); + } + break; + } + case Type::T_OBJECT: { + const auto* obj = (type::Object*)target.Get(); + if ( condition->for_inof_type != ForConditionInOf::FIC_IN && condition->for_inof_type != ForConditionInOf::FIC_OF ) { + THROW( "unexpected for in_of condition type: " + std::to_string( condition->for_inof_type ) ); + } + for ( const auto& v : obj->value ) { + forctx->CreateConst( + condition->variable->name, condition->for_inof_type == ForConditionInOf::FIC_IN + ? VALUE( String, v.first ) + : v.second, &condition->m_si + ); + result = EvaluateScope( forctx, c->body ); + forctx->DestroyVariable( condition->variable->name, &condition->m_si ); + if ( result.Get()->type != Type::T_NOTHING ) { + break; + } + } + break; + } + default: + THROW( "unexpected type for iteration: " + target.ToString() ); + } + forctx->DecRefs(); break; } + default: + THROW( "unexpected for condition type: " + std::to_string( c->condition->for_type ) ); + } + if ( result.Get()->type == Type::T_LOOPCONTROL ) { + return VALUE( Nothing ); // we don't want to break out from parent scope } return result; } @@ -198,6 +311,18 @@ const gse::Value Interpreter::EvaluateExpression( context::Context* ctx, const E *returnflag = true; return Deref( ctx, expression->b->m_si, EvaluateOperand( ctx, expression->b ) ); } + case OT_BREAK: { + ASSERT( returnflag, "break keyword not allowed here" ); + ASSERT( !*returnflag, "already returning" ); + *returnflag = true; + return EvaluateOperand( ctx, expression->b ); + } + case OT_CONTINUE: { + ASSERT( returnflag, "continue keyword not allowed here" ); + ASSERT( !*returnflag, "already returning" ); + *returnflag = true; + return EvaluateOperand( ctx, expression->b ); + } case OT_THROW: { ASSERT( !expression->a, "unexpected left operand before throw" ); const auto& invalid_error_definition = [ &expression, &ctx ]() -> gse::Exception { @@ -395,7 +520,7 @@ const gse::Value Interpreter::EvaluateExpression( context::Context* ctx, const E if ( a->type != b->type ) { \ throw operation_not_supported( a->ToString(), b->ToString() ); \ } \ - gse::Value result = VALUE( Undefined ); \ + gse::Value result = VALUE( Nothing ); \ switch ( a->type ) { \ case Type::T_INT: { \ result = VALUE( Int, ( (Int*)a )->value _op ( (Int*)b )->value ); \ @@ -578,6 +703,9 @@ const gse::Value Interpreter::EvaluateExpression( context::Context* ctx, const E case Type::T_ARRAYRANGEREF: { THROW( "TODO: T_ARRAYRANGEREF" ); } + case Type::T_LOOPCONTROL: { + THROW( "TODO: T_LOOPCONTROL" ); + } case Type::T_OBJECTREF: { const auto arrv = Deref( ctx, expression->a->m_si, refv ); const auto* arr = arrv.Get(); @@ -699,12 +827,20 @@ const gse::Value Interpreter::EvaluateOperand( context::Context* ctx, const Oper for ( const auto& it : call->arguments ) { arguments.push_back( Deref( ctx, it->m_si, EvaluateExpression( ctx, it ) ) ); } - return ( (Callable*)callable.Get() )->Run( ctx, call->m_si, arguments ); + const auto result = ( (Callable*)callable.Get() )->Run( ctx, call->m_si, arguments ); + if ( result.Get()->type == Type::T_NOTHING ) { + // function will return undefined by default + return VALUE( Undefined ); + } + return result; } default: throw gse::Exception( EC.INVALID_CALL, "Callable expected, found: " + callable.ToString(), ctx, call->m_si ); } } + case Operand::OT_LOOP_CONTROL: { + return VALUE( type::LoopControl, ( (program::LoopControl*)operand )->loop_control_type ); + } default: { THROW( "operand " + operand->ToString() + " not implemented" ); } @@ -842,6 +978,35 @@ void Interpreter::ValidateRange( context::Context* ctx, const si_t& si, const ty } } +void Interpreter::CheckBreakCondition( const gse::Value& result, bool* need_break, bool* need_clear ) const { + switch ( result.Get()->type ) { + case Type::T_NOTHING: + *need_break = false; + *need_clear = false; + return; + case Type::T_LOOPCONTROL: { + *need_clear = true; + const auto type = ( (type::LoopControl*)result.Get() )->value; + switch ( type ) { + case program::LCT_BREAK: { + *need_break = true; + return; + } + case program::LCT_CONTINUE: { + *need_break = false; + return; + } + default: + THROW( "unexpected loop control type: " + std::to_string( type ) ); + } + } + default: + // got something to return + *need_break = true; + *need_clear = false; + } +} + Interpreter::Function::Function( const Interpreter* runner, context::Context* context, diff --git a/src/gse/runner/Interpreter.h b/src/gse/runner/Interpreter.h index e974a1b3..34466730 100644 --- a/src/gse/runner/Interpreter.h +++ b/src/gse/runner/Interpreter.h @@ -65,6 +65,7 @@ CLASS( Interpreter, Runner ) const Value Deref( context::Context* ctx, const si_t& si, const Value& value ) const; void WriteByRef( context::Context* ctx, const si_t& si, const Value& ref, const Value& value ) const; void ValidateRange( context::Context* ctx, const si_t& si, const type::Array* array, const std::optional< size_t > from, const std::optional< size_t > to ) const; + void CheckBreakCondition( const gse::Value& result, bool* need_break, bool* need_clear ) const; }; diff --git a/src/gse/tests/Parser.cpp b/src/gse/tests/Parser.cpp index 6e9b5146..ed777011 100644 --- a/src/gse/tests/Parser.cpp +++ b/src/gse/tests/Parser.cpp @@ -6,6 +6,8 @@ #include "gse/program/Statement.h" #include "gse/program/Conditional.h" #include "gse/program/Expression.h" +#include "gse/program/Condition.h" +#include "gse/program/SimpleCondition.h" #include "gse/program/Operator.h" #include "gse/program/Value.h" #include "gse/program/Variable.h" @@ -199,6 +201,25 @@ void AddParserTests( task::gsetests::GSETests* task ) { VALIDATE( operand, a->b, b->b ); GT_OK(); }; + const auto simple_condition = VALIDATOR( SimpleCondition, &errmsg, &expression, &si ) { + VALIDATE( si, a->m_si, b->m_si ); + VALIDATE( expression, a->expression, b->expression ); + GT_OK(); + }; + const auto condition = VALIDATOR( Condition, &errmsg, &simple_condition, &si ) { + GT_ASSERT( a->type == b->type, "conditions have different types ( " + a->ToString() + " != " + b->ToString() + " )" ); + VALIDATE( si, a->m_si, b->m_si ); + switch ( a->type ) { + case Condition::CT_SIMPLE: { + VALIDATE( simple_condition, (SimpleCondition*)a, (SimpleCondition*)b ); + break; + } + default: { + GT_FAIL( "unknown condition type: " + std::to_string( a->type ) ); + } + } + GT_OK(); + }; const auto statement = VALIDATOR( Statement, &errmsg, &expression, &si ) { VALIDATE( si, a->m_si, b->m_si ); VALIDATE( expression, a->body, b->body ); @@ -212,7 +233,7 @@ void AddParserTests( task::gsetests::GSETests* task ) { > conditional = VALIDATOR( Conditional, &errmsg, - &expression, + &condition, &scope, &conditional, &object, @@ -222,12 +243,12 @@ void AddParserTests( task::gsetests::GSETests* task ) { GT_ASSERT( a->conditional_type == b->conditional_type, "conditionals have different types ( " + a->Dump() + " != " + b->Dump() + " )" ); switch ( a->conditional_type ) { case Conditional::CT_IF: { - VALIDATE( expression, ( (If*)a )->condition, ( (If*)b )->condition ); + VALIDATE( condition, ( (If*)a )->condition, ( (If*)b )->condition ); VALIDATE( scope, ( (If*)a )->body, ( (If*)b )->body ); break; } case Conditional::CT_ELSEIF: { - VALIDATE( expression, ( (ElseIf*)a )->condition, ( (ElseIf*)b )->condition ); + VALIDATE( condition, ( (ElseIf*)a )->condition, ( (ElseIf*)b )->condition ); VALIDATE( scope, ( (ElseIf*)a )->body, ( (ElseIf*)b )->body ); break; } @@ -236,10 +257,13 @@ void AddParserTests( task::gsetests::GSETests* task ) { break; } case Conditional::CT_WHILE: { - VALIDATE( expression, ( (While*)a )->condition, ( (While*)b )->condition ); + VALIDATE( condition, ( (While*)a )->condition, ( (While*)b )->condition ); VALIDATE( scope, ( (While*)a )->body, ( (While*)b )->body ); break; } + case Conditional::CT_FOR: { + THROW( "TODO: PARSER FOR" ); + } case Conditional::CT_TRY: { VALIDATE( scope, ( (Try*)a )->body, ( (Try*)b )->body ); VALIDATE( conditional, ( (Try*)a )->handlers, ( (Try*)b )->handlers ); diff --git a/src/gse/tests/Tests.cpp b/src/gse/tests/Tests.cpp index 0a434962..f481b837 100644 --- a/src/gse/tests/Tests.cpp +++ b/src/gse/tests/Tests.cpp @@ -25,6 +25,7 @@ #include "gse/program/Expression.h" #include "gse/program/Operator.h" #include "gse/program/Object.h" +#include "gse/program/SimpleCondition.h" #include "gse/type/Bool.h" #include "gse/type/Int.h" #include "gse/type/String.h" @@ -1463,11 +1464,14 @@ const Program* GetTestProgram() { ), new If( SI( 84, 1, 86, 2 ), - new Expression( - SI( 84, 6, 84, 11 ), - new Variable( SI( 84, 6, 84, 7 ), "a" ), - new Operator( SI( 84, 8, 84, 9 ), OT_GT ), - new Variable( SI( 84, 10, 84, 11 ), "b" ) + new SimpleCondition( + SI( 84, 4, 84, 5 ), + new Expression( + SI( 84, 6, 84, 11 ), + new Variable( SI( 84, 6, 84, 7 ), "a" ), + new Operator( SI( 84, 8, 84, 9 ), OT_GT ), + new Variable( SI( 84, 10, 84, 11 ), "b" ) + ) ), new Scope( SI( 85, 8, 85, 24 ), @@ -1503,11 +1507,14 @@ const Program* GetTestProgram() { ), new If( SI( 90, 1, 92, 2 ), - new Expression( - SI( 90, 6, 90, 11 ), - new Variable( SI( 90, 6, 90, 7 ), "b" ), - new Operator( SI( 90, 8, 90, 9 ), OT_GT ), - new Variable( SI( 90, 10, 90, 11 ), "a" ) + new SimpleCondition( + SI( 90, 4, 90, 5 ), + new Expression( + SI( 90, 6, 90, 11 ), + new Variable( SI( 90, 6, 90, 7 ), "b" ), + new Operator( SI( 90, 8, 90, 9 ), OT_GT ), + new Variable( SI( 90, 10, 90, 11 ), "a" ) + ) ), new Scope( SI( 91, 8, 91, 24 ), @@ -1543,9 +1550,12 @@ const Program* GetTestProgram() { ), new If( SI( 96, 1, 96, 41 ), - new Expression( - SI( 96, 6, 96, 11 ), - new program::Value( SI( 96, 6, 96, 11 ), VALUE( type::Bool, false ) ) + new SimpleCondition( + SI( 96, 4, 96, 5 ), + new Expression( + SI( 96, 6, 96, 11 ), + new program::Value( SI( 96, 6, 96, 11 ), VALUE( type::Bool, false ) ) + ) ), new Scope( SI( 96, 21, 96, 39 ), @@ -1564,9 +1574,12 @@ const Program* GetTestProgram() { ), new If( SI( 97, 1, 99, 2 ), - new Expression( - SI( 97, 6, 97, 11 ), - new program::Value( SI( 97, 6, 97, 11 ), VALUE( type::Bool, false ) ) + new SimpleCondition( + SI( 97, 4, 97, 5 ), + new Expression( + SI( 97, 6, 97, 11 ), + new program::Value( SI( 97, 6, 97, 11 ), VALUE( type::Bool, false ) ) + ) ), new Scope( SI( 98, 8, 98, 23 ), @@ -1584,9 +1597,12 @@ const Program* GetTestProgram() { ), new ElseIf( SI( 99, 3, 101, 2 ), - new Expression( - SI( 99, 12, 99, 17 ), - new program::Value( SI( 99, 12, 99, 17 ), VALUE( type::Bool, false ) ) + new SimpleCondition( + SI( 99, 3, 101, 2 ), + new Expression( + SI( 99, 12, 99, 17 ), + new program::Value( SI( 99, 12, 99, 17 ), VALUE( type::Bool, false ) ) + ) ), new Scope( SI( 100, 3, 100, 25 ), @@ -1604,9 +1620,12 @@ const Program* GetTestProgram() { ), new ElseIf( SI( 101, 3, 103, 2 ), - new Expression( - SI( 101, 12, 101, 16 ), - new program::Value( SI( 101, 12, 101, 16 ), VALUE( type::Bool, true ) ) + new SimpleCondition( + SI( 101, 3, 103, 2 ), + new Expression( + SI( 101, 12, 101, 16 ), + new program::Value( SI( 101, 12, 101, 16 ), VALUE( type::Bool, true ) ) + ) ), new Scope( SI( 102, 3, 102, 23 ), @@ -1653,15 +1672,18 @@ const Program* GetTestProgram() { ), new While( SI( 108, 1, 110, 2 ), - new Expression( - SI( 108, 9, 108, 16 ), + new SimpleCondition( + SI( 108, 7, 108, 8 ), new Expression( - SI( 108, 9, 108, 12 ), - new Variable( SI( 108, 9, 108, 10 ), "i" ), - new Operator( SI( 108, 10, 108, 12 ), OT_INC ) - ), - new Operator( SI( 108, 13, 108, 14 ), OT_LT ), - new program::Value( SI( 108, 15, 108, 16 ), VALUE( type::Int, 5 ) ) + SI( 108, 9, 108, 16 ), + new Expression( + SI( 108, 9, 108, 12 ), + new Variable( SI( 108, 9, 108, 10 ), "i" ), + new Operator( SI( 108, 10, 108, 12 ), OT_INC ) + ), + new Operator( SI( 108, 13, 108, 14 ), OT_LT ), + new program::Value( SI( 108, 15, 108, 16 ), VALUE( type::Int, 5 ) ) + ) ), new Scope( SI( 109, 8, 109, 18 ), diff --git a/src/gse/type/LoopControl.h b/src/gse/type/LoopControl.h new file mode 100644 index 00000000..5af4cdd0 --- /dev/null +++ b/src/gse/type/LoopControl.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Type.h" + +#include "gse/program/Types.h" + +namespace gse { +namespace type { + +class LoopControl : public Type { +public: + + static const type_t GetType() { return Type::T_LOOPCONTROL; } + + LoopControl( const program::loop_control_type_t initial_value ) + : Type( GetType() ) + , value( initial_value ) { + } + + program::loop_control_type_t value; +}; + +} +} diff --git a/src/gse/type/Nothing.h b/src/gse/type/Nothing.h new file mode 100644 index 00000000..4af5d1a5 --- /dev/null +++ b/src/gse/type/Nothing.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Type.h" + +namespace gse { +namespace type { + +class Nothing : public Type { +public: + + static const type_t GetType() { return Type::T_NOTHING; } + + Nothing() + : Type( GetType() ) {} + + Nothing( const Nothing& other ) + : Nothing() {} +}; + +} +} diff --git a/src/gse/type/Object.cpp b/src/gse/type/Object.cpp index e146d946..343303a4 100644 --- a/src/gse/type/Object.cpp +++ b/src/gse/type/Object.cpp @@ -47,6 +47,10 @@ static const std::unordered_map< Object::object_class_t, std::string > s_object_ Object::CLASS_UNIT, "#unit" }, + { + Object::CLASS_BASE, + "#base" + }, }; const std::string& Object::GetClassString( const object_class_t object_class ) { const auto& it = s_object_class_str.find( object_class ); diff --git a/src/gse/type/Type.cpp b/src/gse/type/Type.cpp index 6440bdfb..5f75a8e1 100644 --- a/src/gse/type/Type.cpp +++ b/src/gse/type/Type.cpp @@ -15,6 +15,7 @@ #include "ObjectRef.h" #include "Range.h" #include "Exception.h" +#include "LoopControl.h" #include "types/Buffer.h" @@ -34,6 +35,7 @@ static const std::string s_t_arrayref = "Arrayref"; static const std::string s_t_arrayrangeref = "Arrayrangeref"; static const std::string s_t_objectref = "Objectref"; static const std::string s_t_range = "Range"; +static const std::string s_t_loopcontrol = "LoopControl"; static const std::string s_t_unknown = "Unknown"; const std::string& Type::GetTypeString( const type_t type ) { switch ( type ) { @@ -63,6 +65,8 @@ const std::string& Type::GetTypeString( const type_t type ) { return s_t_objectref; case T_RANGE: return s_t_range; + case T_LOOPCONTROL: + return s_t_loopcontrol; default: return s_t_unknown; } @@ -143,6 +147,16 @@ const std::string Type::ToString() const { : "" ) + "]"; } + case T_LOOPCONTROL: { + switch ( ( (LoopControl*)this )->value ) { + case program::LCT_BREAK: + return "break"; + case program::LCT_CONTINUE: + return "continue"; + default: + THROW( "unexpected loop control type: " + std::to_string( ( (LoopControl*)this )->value ) ); + } + } default: THROW( "unknown is not intended to be printed" ); } @@ -232,6 +246,9 @@ const std::string Type::Dump() const { : "" ) + "}"; } + case T_LOOPCONTROL: { + return "loopcontrol{" + ToString() + "}"; + } default: return "unknown{" + std::to_string( type ) + "}"; } diff --git a/src/gse/type/Type.h b/src/gse/type/Type.h index e3aab79c..583f98da 100644 --- a/src/gse/type/Type.h +++ b/src/gse/type/Type.h @@ -14,6 +14,7 @@ namespace type { class Type { public: enum type_t : uint8_t { + T_NOTHING, T_UNDEFINED, T_NULL, T_BOOL, @@ -27,6 +28,7 @@ class Type { T_ARRAYRANGEREF, T_OBJECTREF, T_RANGE, + T_LOOPCONTROL, }; static const std::string& GetTypeString( const type_t type ); diff --git a/src/loader/CMakeLists.txt b/src/loader/CMakeLists.txt index 85d02293..8c8793fc 100644 --- a/src/loader/CMakeLists.txt +++ b/src/loader/CMakeLists.txt @@ -1,6 +1,7 @@ SUBDIR( font ) SUBDIR( texture ) SUBDIR( sound ) +SUBDIR( txt ) SET( SRC ${SRC} diff --git a/src/loader/Loader.cpp b/src/loader/Loader.cpp index bb7884ab..724a4239 100644 --- a/src/loader/Loader.cpp +++ b/src/loader/Loader.cpp @@ -6,6 +6,10 @@ namespace loader { const std::string& Loader::GetFilename( const resource::resource_t res ) const { + return g_engine->GetResourceManager()->GetFilename( res ); +} + +const std::string& Loader::GetPath( const resource::resource_t res ) const { return g_engine->GetResourceManager()->GetPath( res ); } diff --git a/src/loader/Loader.h b/src/loader/Loader.h index 0d6787af..378308ae 100644 --- a/src/loader/Loader.h +++ b/src/loader/Loader.h @@ -12,6 +12,7 @@ CLASS( Loader, common::Module ) protected: const std::string& GetFilename( const resource::resource_t res ) const; + const std::string& GetPath( const resource::resource_t res ) const; const std::string& GetCustomFilename( const std::string& filename ) const; }; diff --git a/src/loader/font/FontLoader.cpp b/src/loader/font/FontLoader.cpp index 4919c57c..0aaa3e0c 100644 --- a/src/loader/font/FontLoader.cpp +++ b/src/loader/font/FontLoader.cpp @@ -4,7 +4,7 @@ namespace loader { namespace font { types::Font* FontLoader::LoadFont( const resource::resource_t res, const unsigned char size ) { - return LoadFontImpl( GetFilename( res ), size ); + return LoadFontImpl( GetFilename( res ), GetPath( res ), size ); } } diff --git a/src/loader/font/FontLoader.h b/src/loader/font/FontLoader.h index 65cf1cac..7f231d19 100644 --- a/src/loader/font/FontLoader.h +++ b/src/loader/font/FontLoader.h @@ -16,7 +16,7 @@ namespace font { CLASS( FontLoader, Loader ) types::Font* LoadFont( const resource::resource_t res, const unsigned char size ); protected: - virtual types::Font* LoadFontImpl( const std::string& filename, const unsigned char size ) = 0; + virtual types::Font* LoadFontImpl( const std::string& name, const std::string& path, const unsigned char size ) = 0; }; } diff --git a/src/loader/font/FreeType.cpp b/src/loader/font/FreeType.cpp index 0f2d7860..7c7d68d5 100644 --- a/src/loader/font/FreeType.cpp +++ b/src/loader/font/FreeType.cpp @@ -25,9 +25,9 @@ void FreeType::Iterate() { } -types::Font* FreeType::LoadFontImpl( const std::string& filename, const unsigned char size ) { +types::Font* FreeType::LoadFontImpl( const std::string& name, const std::string& path, const unsigned char size ) { - std::string font_key = filename + ":" + std::to_string( size ); + std::string font_key = name + ":" + std::to_string( size ); font_map_t::iterator it = m_fonts.find( font_key ); if ( it != m_fonts.end() ) { @@ -38,12 +38,11 @@ types::Font* FreeType::LoadFontImpl( const std::string& filename, const unsigned Log( "Loading font \"" + font_key + "\"" ); - NEWV( font, types::Font ); - font->m_name = filename; + NEWV( font, types::Font, font_key ); FT_Face ftface; - res = FT_New_Face( m_freetype, filename.c_str(), 0, &ftface ); - ASSERT( !res, "Unable to load font \"" + filename + "\"" ); + res = FT_New_Face( m_freetype, path.c_str(), 0, &ftface ); + ASSERT( !res, "Unable to load font \"" + path + "\"" ); FT_Set_Pixel_Sizes( ftface, 0, size ); @@ -55,7 +54,7 @@ types::Font* FreeType::LoadFontImpl( const std::string& filename, const unsigned for ( int i = 32 ; i < 128 ; i++ ) { // only ascii for now res = FT_Load_Char( ftface, i, FT_LOAD_RENDER ); - ASSERT( !res, "Font \"" + filename + "\" bitmap loading failed" ); + ASSERT( !res, "Font \"" + path + "\" bitmap loading failed" ); bitmap = &font->m_symbols[ i ]; diff --git a/src/loader/font/FreeType.h b/src/loader/font/FreeType.h index b5565d4b..f9c133bb 100644 --- a/src/loader/font/FreeType.h +++ b/src/loader/font/FreeType.h @@ -20,7 +20,7 @@ CLASS( FreeType, FontLoader ) protected: - types::Font* LoadFontImpl( const std::string& filename, const unsigned char size ) override; + types::Font* LoadFontImpl( const std::string& name, const std::string& path, const unsigned char size ) override; private: FT_Library m_freetype; diff --git a/src/loader/font/Null.h b/src/loader/font/Null.h index df8cfd06..0af2b704 100644 --- a/src/loader/font/Null.h +++ b/src/loader/font/Null.h @@ -9,7 +9,7 @@ namespace font { CLASS( Null, FontLoader ) protected: - types::Font* LoadFontImpl( const std::string& filename, const unsigned char size ) override { return nullptr; } + types::Font* LoadFontImpl( const std::string& name, const std::string& path, const unsigned char size ) override { return nullptr; } }; } diff --git a/src/loader/sound/SoundLoader.cpp b/src/loader/sound/SoundLoader.cpp index 694dd43d..f8860375 100644 --- a/src/loader/sound/SoundLoader.cpp +++ b/src/loader/sound/SoundLoader.cpp @@ -4,7 +4,7 @@ namespace loader { namespace sound { types::Sound* SoundLoader::LoadSound( const resource::resource_t res ) { - return LoadSoundImpl( GetFilename( res ) ); + return LoadSoundImpl( GetPath( res ) ); } types::Sound* SoundLoader::LoadCustomSound( const std::string& filename ) { diff --git a/src/loader/texture/TextureLoader.cpp b/src/loader/texture/TextureLoader.cpp index 9204713a..923391c5 100644 --- a/src/loader/texture/TextureLoader.cpp +++ b/src/loader/texture/TextureLoader.cpp @@ -250,7 +250,7 @@ types::texture::Texture* TextureLoader::LoadTexture( const resource::resource_t const bool fix_yellow_shadows_old = m_fix_yellow_shadows; m_transparent_colors = GetTCs( res ); m_fix_yellow_shadows = s_fix_yellow_shadow.find( res ) != s_fix_yellow_shadow.end(); - auto* result = LoadTextureImpl( GetFilename( res ) ); + auto* result = LoadTextureImpl( GetPath( res ) ); m_transparent_colors = colors_old; m_fix_yellow_shadows = fix_yellow_shadows_old; return result; @@ -279,7 +279,7 @@ types::texture::Texture* TextureLoader::LoadCustomTexture( const std::string& fi types::texture::Texture* TextureLoader::LoadTexture( const resource::resource_t res, const size_t x1, const size_t y1, const size_t x2, const size_t y2, const uint8_t flags, const float value ) { const transparent_colors_t colors_old = m_transparent_colors; m_transparent_colors = GetTCs( res ); - types::texture::Texture* result = LoadTextureImpl( GetFilename( res ), x1, y1, x2, y2, flags, value ); + types::texture::Texture* result = LoadTextureImpl( GetPath( res ), x1, y1, x2, y2, flags, value ); m_transparent_colors = colors_old; return result; } diff --git a/src/loader/txt/CMakeLists.txt b/src/loader/txt/CMakeLists.txt new file mode 100644 index 00000000..fb3fc14f --- /dev/null +++ b/src/loader/txt/CMakeLists.txt @@ -0,0 +1,7 @@ +SET( SRC ${SRC} + + ${PWD}/TXTLoaders.cpp + ${PWD}/TXTLoader.cpp + ${PWD}/FactionTXTLoader.cpp + + PARENT_SCOPE ) diff --git a/src/loader/txt/FactionTXTLoader.cpp b/src/loader/txt/FactionTXTLoader.cpp new file mode 100644 index 00000000..26fc5e1b --- /dev/null +++ b/src/loader/txt/FactionTXTLoader.cpp @@ -0,0 +1,45 @@ +#include "FactionTXTLoader.h" + +#include "engine/Engine.h" +#include "resource/ResourceManager.h" + +namespace loader { +namespace txt { + +const FactionTXTLoader::faction_data_t& FactionTXTLoader::GetFactionData( resource::resource_t res ) { + return GetFactionDataImpl( g_engine->GetResourceManager()->GetPath( res ) ); +} + +const FactionTXTLoader::faction_data_t& FactionTXTLoader::GetFactionData( const std::string& filename ) { + return GetFactionDataImpl( g_engine->GetResourceManager()->GetCustomPath( filename ) ); +} + +const FactionTXTLoader::faction_data_t& FactionTXTLoader::GetFactionDataImpl( const std::string& path ) { + auto it = m_faction_data.find( path ); + if ( it == m_faction_data.end() ) { + const auto data = GetTXTData( path ); + it = m_faction_data.insert( + { + path, + faction_data_t{ + { + GetSection( data, "BASES" ), + GetSection( data, "WATERBASES" ), + } + } + } + ).first; + } + return it->second; +} + +const std::vector< std::string >& FactionTXTLoader::GetSection( const txt_data_t& data, const std::string& name ) const { + const auto bases_it = data.sections.find( name ); + if ( bases_it == data.sections.end() ) { + THROW( "file does not contain section #" + name ); + } + return bases_it->second; +} + +} +} diff --git a/src/loader/txt/FactionTXTLoader.h b/src/loader/txt/FactionTXTLoader.h new file mode 100644 index 00000000..b809f8ca --- /dev/null +++ b/src/loader/txt/FactionTXTLoader.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "TXTLoader.h" + +namespace loader { +namespace txt { + +CLASS( FactionTXTLoader, TXTLoader ) + + struct faction_data_t { + struct { + std::vector< std::string > land; + std::vector< std::string > water; + } bases_names; + }; + const faction_data_t& GetFactionData( resource::resource_t res ); + const faction_data_t& GetFactionData( const std::string& filename ); + +protected: + +private: + + std::unordered_map< std::string, faction_data_t > m_faction_data = {}; + + const faction_data_t& GetFactionDataImpl( const std::string& path ); + const std::vector< std::string >& GetSection( const txt_data_t& data, const std::string& name ) const; + +}; + +} +} diff --git a/src/loader/txt/TXTLoader.cpp b/src/loader/txt/TXTLoader.cpp new file mode 100644 index 00000000..de98eefd --- /dev/null +++ b/src/loader/txt/TXTLoader.cpp @@ -0,0 +1,55 @@ +#include "TXTLoader.h" + +#include "util/FS.h" +#include "util/String.h" + +namespace loader { +namespace txt { + +const TXTLoader::txt_data_t& TXTLoader::GetTXTData( const std::string& path ) { + auto it = m_txt_data.find( path ); + if ( it == m_txt_data.end() ) { + it = m_txt_data.insert( + { + path, + txt_data_t{ util::FS::ReadFile( path ) } + } + ).first; + } + return it->second; +} + +TXTLoader::txt_data_t::txt_data_t( const std::string& source ) { + + // raw lines + lines = util::String::SplitToLines( source ); + + // sections + sections_t::iterator section_it = sections.end(); + for ( const auto& line : lines ) { + if ( !line.empty() && line[ 0 ] == '#' && line.find( ' ' ) == std::string::npos ) { + if ( line.substr( 0, 4 ) == "#END" ) { + section_it = sections.end(); + } + else { + const auto section_name = line.substr( 1 ); + ASSERT_NOLOG( sections.find( section_name ) == sections.end(), "section '" + section_name + "' already exists" ); + section_it = sections.insert( + { + section_name, + {} + } + ).first; + } + } + else { + if ( !line.empty() && section_it != sections.end() ) { + section_it->second.push_back( line ); + } + } + } + +} + +} +} diff --git a/src/loader/txt/TXTLoader.h b/src/loader/txt/TXTLoader.h new file mode 100644 index 00000000..2c701c51 --- /dev/null +++ b/src/loader/txt/TXTLoader.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "loader/Loader.h" + +namespace loader { +namespace txt { + +CLASS( TXTLoader, Loader ) + +protected: + + struct txt_data_t { + typedef std::vector< std::string > lines_t; + typedef std::unordered_map< std::string, std::vector< std::string > > sections_t; + txt_data_t( const std::string& source ); + lines_t lines = {}; + sections_t sections = {}; + }; + + std::unordered_map< std::string, txt_data_t > m_txt_data = {}; + + const txt_data_t& GetTXTData( const std::string& path ); +}; + +} +} diff --git a/src/loader/txt/TXTLoaders.cpp b/src/loader/txt/TXTLoaders.cpp new file mode 100644 index 00000000..ac4287a2 --- /dev/null +++ b/src/loader/txt/TXTLoaders.cpp @@ -0,0 +1,17 @@ +#include "TXTLoaders.h" + +#include "FactionTXTLoader.h" + +namespace loader { +namespace txt { + +TXTLoaders::TXTLoaders() + : factions( new FactionTXTLoader() ) { + // +} +TXTLoaders::~TXTLoaders() { + delete factions; +} + +} +} \ No newline at end of file diff --git a/src/loader/txt/TXTLoaders.h b/src/loader/txt/TXTLoaders.h new file mode 100644 index 00000000..15e0cf37 --- /dev/null +++ b/src/loader/txt/TXTLoaders.h @@ -0,0 +1,17 @@ +#pragma once + +namespace loader { +namespace txt { + +class FactionTXTLoader; + +class TXTLoaders { +public: + TXTLoaders(); + ~TXTLoaders(); + + FactionTXTLoader* const factions; +}; + +} +} diff --git a/src/main.cpp b/src/main.cpp index 637dd5a7..f8c555f9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -37,6 +37,7 @@ #include "loader/font/FreeType.h" #include "loader/texture/SDL2.h" #include "loader/sound/SDL2.h" +#include "loader/txt/TXTLoaders.h" #include "input/sdl2/SDL2.h" #include "graphics/opengl/OpenGL.h" #include "audio/sdl2/SDL2.h" @@ -98,7 +99,6 @@ int main( const int argc, const char* argv[] ) { config.Init(); - #ifdef DEBUG if ( config.HasDebugFlag( config::Config::DF_GDB ) ) { #ifdef __linux__ @@ -200,6 +200,7 @@ int main( const int argc, const char* argv[] ) { &font_loader, &texture_loader, &sound_loader, + nullptr, &scheduler, &input, &graphics, @@ -221,6 +222,7 @@ int main( const int argc, const char* argv[] ) { loader::font::FreeType font_loader; loader::texture::SDL2 texture_loader; loader::sound::SDL2 sound_loader; + loader::txt::TXTLoaders txt_loaders; input::sdl2::SDL2 input; bool vsync = VSYNC; @@ -265,6 +267,7 @@ int main( const int argc, const char* argv[] ) { &font_loader, &texture_loader, &sound_loader, + &txt_loaders, &scheduler, &input, &graphics, diff --git a/src/resource/ResourceManager.cpp b/src/resource/ResourceManager.cpp index 3e37af8b..6f54e0ed 100644 --- a/src/resource/ResourceManager.cpp +++ b/src/resource/ResourceManager.cpp @@ -279,6 +279,11 @@ const resource_t ResourceManager::GetResource( const std::string& filename ) con return NONE; } +const std::string& ResourceManager::GetFilename( const resource_t res ) const { + ASSERT_NOLOG( m_resources_to_filenames.find( res ) != m_resources_to_filenames.end(), "filename for " + std::to_string( res ) + " not found" ); + return m_resources_to_filenames.at( res ); +} + const std::string& ResourceManager::GetPath( const resource_t res ) const { ASSERT_NOLOG( m_resource_paths.find( res ) != m_resource_paths.end(), "resource path for " + std::to_string( res ) + " not found" ); return m_resource_paths.at( res ); diff --git a/src/resource/ResourceManager.h b/src/resource/ResourceManager.h index d7bc05ca..00c3d564 100644 --- a/src/resource/ResourceManager.h +++ b/src/resource/ResourceManager.h @@ -16,6 +16,7 @@ CLASS( ResourceManager, common::Module ) void Init( std::vector< std::string > possible_smac_paths ); const resource_t GetResource( const std::string& filename ) const; + const std::string& GetFilename( const resource_t res ) const; const std::string& GetPath( const resource_t res ) const; const std::string& GetCustomPath( const std::string& path ); diff --git a/src/resource/Types.h b/src/resource/Types.h index 418ad50e..3aee662a 100644 --- a/src/resource/Types.h +++ b/src/resource/Types.h @@ -1,5 +1,7 @@ #pragma once +#include + namespace resource { enum resource_t : uint8_t { @@ -56,7 +58,7 @@ enum resource_t : uint8_t { PCX_FUNGBOY, PCX_CARETAKE, PCX_USURPER, - + TTF_ARIALN, TTF_ARIALNB, diff --git a/src/task/game/Animation.cpp b/src/task/game/Animation.cpp index 29df019e..cc31ae86 100644 --- a/src/task/game/Animation.cpp +++ b/src/task/game/Animation.cpp @@ -4,7 +4,7 @@ #include "AnimationDef.h" #include "scene/actor/Sound.h" #include "audio/Audio.h" -#include "InstancedSprite.h" +#include "task/game/sprite/InstancedSprite.h" #include "scene/actor/Instanced.h" namespace task { diff --git a/src/task/game/AnimationDef.cpp b/src/task/game/AnimationDef.cpp index 6dc22c73..87990e74 100644 --- a/src/task/game/AnimationDef.cpp +++ b/src/task/game/AnimationDef.cpp @@ -4,11 +4,12 @@ #include "game/animation/FramesRow.h" #include "loader/sound/SoundLoader.h" #include "loader/texture/TextureLoader.h" +#include "task/game/sprite/InstancedSpriteManager.h" namespace task { namespace game { -AnimationDef::AnimationDef( InstancedSpriteManager* ism, const ::game::animation::Def* def ) +AnimationDef::AnimationDef( sprite::InstancedSpriteManager* ism, const ::game::animation::Def* def ) : m_ism( ism ) , m_id( def->m_id ) , m_type( def->m_type ) @@ -61,7 +62,7 @@ const instanced_sprites_t& AnimationDef::GetSprites() { y + cxy.y }, dst_wh, - InstancedSpriteManager::ZL_ANIMATIONS + ZL_ANIMATIONS ) ); } diff --git a/src/task/game/AnimationDef.h b/src/task/game/AnimationDef.h index fb10edd3..9740921f 100644 --- a/src/task/game/AnimationDef.h +++ b/src/task/game/AnimationDef.h @@ -1,7 +1,5 @@ #pragma once -#include "InstancedSpriteManager.h" - #include "Types.h" #include "game/animation/Types.h" @@ -16,10 +14,14 @@ class Def; namespace task { namespace game { +namespace sprite { +class InstancedSpriteManager; +} + class AnimationDef { public: - AnimationDef( InstancedSpriteManager* ism, const ::game::animation::Def* def ); + AnimationDef( sprite::InstancedSpriteManager* ism, const ::game::animation::Def* def ); ~AnimationDef(); const instanced_sprites_t& GetSprites(); @@ -28,7 +30,7 @@ class AnimationDef { private: - InstancedSpriteManager* const m_ism = nullptr; + sprite::InstancedSpriteManager* const m_ism = nullptr; ::game::animation::sprite_render_info_t m_render = {}; diff --git a/src/task/game/CMakeLists.txt b/src/task/game/CMakeLists.txt index 7b2fd186..de4713ee 100644 --- a/src/task/game/CMakeLists.txt +++ b/src/task/game/CMakeLists.txt @@ -2,16 +2,16 @@ SUBDIR( ui ) SUBDIR( actor ) SUBDIR( faction ) SUBDIR( tile ) +SUBDIR( sprite ) +SUBDIR( text ) SUBDIR( unit ) SUBDIR( base ) SET( SRC ${SRC} ${PWD}/Game.cpp - ${PWD}/InstancedSpriteManager.cpp - ${PWD}/InstancedSprite.cpp - ${PWD}/Sprite.cpp ${PWD}/Slot.cpp + ${PWD}/TileObject.cpp ${PWD}/AnimationDef.cpp ${PWD}/Animation.cpp diff --git a/src/task/game/Game.cpp b/src/task/game/Game.cpp index a7e5459c..d0504a99 100644 --- a/src/task/game/Game.cpp +++ b/src/task/game/Game.cpp @@ -28,7 +28,8 @@ #include "unit/UnitManager.h" #include "base/BaseManager.h" #include "faction/FactionManager.h" -#include "InstancedSpriteManager.h" +#include "task/game/sprite/InstancedSpriteManager.h" +#include "task/game/text/InstancedTextManager.h" #include "Animation.h" #include "AnimationDef.h" #include "task/game/unit/Unit.h" @@ -36,7 +37,7 @@ #include "Slot.h" #include "task/game/unit/BadgeDefs.h" #include "scene/actor/Instanced.h" -#include "InstancedSprite.h" +#include "task/game/sprite/InstancedSprite.h" #include "loader/texture/TextureLoader.h" #include "actor/Actor.h" #include "actor/TileSelection.h" @@ -49,6 +50,10 @@ #include "ui/style/Theme.h" #include "game/map/Consts.h" +// TMP +#include "loader/font/FontLoader.h" +#include "text/InstancedText.h" + #define INITIAL_CAMERA_ANGLE { -M_PI * 0.5, M_PI * 0.75, 0 } namespace task { @@ -482,11 +487,16 @@ base::BaseManager* Game::GetBM() const { return m_bm; } -InstancedSpriteManager* Game::GetISM() const { +sprite::InstancedSpriteManager* Game::GetISM() const { ASSERT( m_ism, "ism not set" ); return m_ism; } +text::InstancedTextManager* Game::GetITM() const { + ASSERT( m_itm, "itm not set" ); + return m_itm; +} + types::texture::Texture* Game::GetSourceTexture( const resource::resource_t res ) { const auto it = m_textures.source.find( res ); if ( it != m_textures.source.end() ) { @@ -503,7 +513,7 @@ types::texture::Texture* Game::GetSourceTexture( const resource::resource_t res return texture; } -InstancedSprite* Game::GetTerrainInstancedSprite( const ::game::map::sprite_actor_t& actor ) { +sprite::InstancedSprite* Game::GetTerrainInstancedSprite( const ::game::map::sprite_actor_t& actor ) { return m_ism->GetInstancedSprite( "Terrain_" + actor.name, GetSourceTexture( resource::PCX_TER1 ), @@ -517,7 +527,7 @@ InstancedSprite* Game::GetTerrainInstancedSprite( const ::game::map::sprite_acto ::game::map::s_consts.tile.scale.x, ::game::map::s_consts.tile.scale.y * ::game::map::s_consts.sprite.y_scale }, - InstancedSpriteManager::ZL_TERRAIN, + ZL_TERRAIN, actor.z_index ); } @@ -1069,6 +1079,10 @@ void Game::ProcessRequest( const ::game::FrontendRequest* request ) { rc.x, rc.y, rc.z + }, + { + *d.base_info.name, + d.base_info.population } ); break; @@ -1083,14 +1097,6 @@ void Game::SendBackendRequest( const ::game::BackendRequest* request ) { m_pending_backend_requests.push_back( *request ); } -void Game::ActivateTurn() { - -} - -void Game::DeactivateTurn() { - -} - void Game::UpdateMapData( const types::Vec2< size_t >& map_size ) { m_map_data.width = map_size.x; @@ -1141,7 +1147,8 @@ void Game::Initialize( NEW( m_world_scene, scene::Scene, "Game", scene::SCENE_TYPE_ORTHO ); - NEW( m_ism, InstancedSpriteManager, m_world_scene ); + NEW( m_ism, sprite::InstancedSpriteManager, m_world_scene ); + NEW( m_itm, text::InstancedTextManager, m_ism ); NEW( m_fm, faction::FactionManager, this ); NEW( m_tm, tile::TileManager, this ); NEW( m_um, unit::UnitManager, this ); @@ -1705,6 +1712,11 @@ void Game::Deinitialize() { m_tm = nullptr; } + if ( m_itm ) { + DELETE( m_itm ); + m_itm = nullptr; + } + if ( m_ism ) { DELETE( m_ism ); m_ism = nullptr; @@ -1811,6 +1823,7 @@ void Game::DeselectTileOrUnit() { HideTileSelector(); m_um->DeselectUnit(); + m_tm->DeselectTile(); m_ui.bottom_bar->HideTilePreview(); } diff --git a/src/task/game/Game.h b/src/task/game/Game.h index ac645e69..e105b3a0 100644 --- a/src/task/game/Game.h +++ b/src/task/game/Game.h @@ -94,9 +94,16 @@ class BaseManager; class Base; } +namespace sprite { class InstancedSpriteManager; class InstancedSprite; class Sprite; +} + +namespace text { +class InstancedTextManager; +} + class Slot; class AnimationDef; class Animation; @@ -161,10 +168,11 @@ CLASS( Game, common::Task ) tile::TileManager* GetTM() const; unit::UnitManager* GetUM() const; base::BaseManager* GetBM() const; - InstancedSpriteManager* GetISM() const; + sprite::InstancedSpriteManager* GetISM() const; + text::InstancedTextManager* GetITM() const; types::texture::Texture* GetSourceTexture( const resource::resource_t res ); - InstancedSprite* GetTerrainInstancedSprite( const ::game::map::sprite_actor_t& actor ); + sprite::InstancedSprite* GetTerrainInstancedSprite( const ::game::map::sprite_actor_t& actor ); void CenterAtCoordinatePercents( const ::types::Vec2< float > position_percents ); @@ -231,14 +239,13 @@ CLASS( Game, common::Task ) tile::TileManager* m_tm = nullptr; unit::UnitManager* m_um = nullptr; base::BaseManager* m_bm = nullptr; - InstancedSpriteManager* m_ism = nullptr; + sprite::InstancedSpriteManager* m_ism = nullptr; + text::InstancedTextManager* m_itm = nullptr; size_t m_slot_index = 0; bool m_is_turn_active = false; ::game::turn::turn_status_t m_turn_status = ::game::turn::TS_PLEASE_WAIT; size_t m_turn_id = 0; - void ActivateTurn(); - void DeactivateTurn(); ::game::map_editor::tool_type_t m_editor_tool = ::game::map_editor::TT_NONE; ::game::map_editor::brush_type_t m_editor_brush = ::game::map_editor::BT_NONE; diff --git a/src/task/game/InstancedSprite.cpp b/src/task/game/InstancedSprite.cpp deleted file mode 100644 index a64e1866..00000000 --- a/src/task/game/InstancedSprite.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "InstancedSprite.h" - -namespace task { -namespace game { - -} -} diff --git a/src/task/game/Sprite.cpp b/src/task/game/Sprite.cpp deleted file mode 100644 index a4428a7a..00000000 --- a/src/task/game/Sprite.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "Sprite.h" - -namespace task { -namespace game { - -} -} diff --git a/src/task/game/TileObject.cpp b/src/task/game/TileObject.cpp new file mode 100644 index 00000000..9ffbdb7f --- /dev/null +++ b/src/task/game/TileObject.cpp @@ -0,0 +1,19 @@ +#include "TileObject.h" + +#include "tile/Tile.h" + +namespace task { +namespace game { + +TileObject::TileObject( tile::Tile* tile ) + : m_tile( tile ) { + // +} + +void TileObject::UpdateFromTile() { + ASSERT_NOLOG( m_tile, "tile not set" ); + SetRenderCoords( m_tile->GetRenderData().coords.InvertY() ); +} + +} +} diff --git a/src/task/game/TileObject.h b/src/task/game/TileObject.h new file mode 100644 index 00000000..ec5a18d8 --- /dev/null +++ b/src/task/game/TileObject.h @@ -0,0 +1,29 @@ +#pragma once + +#include "types/Vec3.h" + +namespace task { +namespace game { + +namespace tile { +class Tile; +} + +class TileObject { +public: + TileObject( tile::Tile* tile ); + + void UpdateFromTile(); + +protected: + + tile::Tile* m_tile = nullptr; + + virtual void SetRenderCoords( const types::Vec3& coords ) = 0; + +private: + +}; + +} +} \ No newline at end of file diff --git a/src/task/game/Types.h b/src/task/game/Types.h index 3171e32c..e320fc8f 100644 --- a/src/task/game/Types.h +++ b/src/task/game/Types.h @@ -1,13 +1,48 @@ #pragma once #include +#include namespace task { namespace game { +namespace sprite { class InstancedSprite; +} + +typedef std::vector< sprite::InstancedSprite* > instanced_sprites_t; + +enum z_level_t { + ZL_TERRAIN, + ZL_TERRAIN_TEXT, + ZL_BASES, + ZL_UNITS, + ZL_ANIMATIONS, +}; +static const float MAX_ZINDEX_ADJUSTMENT = 0.05f; -typedef std::vector< InstancedSprite* > instanced_sprites_t; +static const std::unordered_map< z_level_t, float > s_zlevel_map = { + { + ZL_TERRAIN, + 0.4f + }, + { + ZL_TERRAIN_TEXT, + 0.8f + }, + { + ZL_BASES, + 0.5f + }, + { + ZL_UNITS, + 0.6f + }, + { + ZL_ANIMATIONS, + 0.7f + }, +}; } } diff --git a/src/task/game/base/Base.cpp b/src/task/game/base/Base.cpp index a242e6af..2cee9567 100644 --- a/src/task/game/base/Base.cpp +++ b/src/task/game/base/Base.cpp @@ -6,8 +6,9 @@ #include "util/String.h" #include "game/base/Base.h" #include "task/game/Slot.h" -#include "task/game/Sprite.h" -#include "task/game/InstancedSprite.h" +#include "task/game/sprite/Sprite.h" +#include "task/game/sprite/InstancedSprite.h" +#include "task/game/text/InstancedText.h" #include "scene/actor/Instanced.h" #include "types/mesh/Rectangle.h" #include "scene/actor/Sprite.h" @@ -17,26 +18,35 @@ namespace task { namespace game { namespace base { +static const std::vector< uint8_t > s_base_render_population_thresholds = { + 1, + 4, + 8, + 15, +}; + Base::Base( - BaseManager* bm, const size_t id, Slot* slot, tile::Tile* tile, + const bool is_owned, const types::Vec3& render_coords, - const bool is_owned + text::InstancedText* render_name_sprite, + size_t population ) - : m_bm( bm ) + : TileObject( tile ) , m_id( id ) , m_faction( slot->GetFaction() ) - , m_tile( tile ) , m_render( { render_coords, + render_name_sprite, false, 0, } ) - , m_is_owned( is_owned ) { + , m_is_owned( is_owned ) + , m_population( population ) { m_render_data.base = GetMeshTex( GetSprite()->instanced_sprite ); m_tile->SetBase( this ); } @@ -44,6 +54,7 @@ Base::Base( Base::~Base() { Hide(); m_tile->UnsetBase( this ); + delete m_render.name_sprite; } const size_t Base::GetId() const { @@ -58,15 +69,22 @@ tile::Tile* Base::GetTile() const { return m_tile; } -Sprite* Base::GetSprite() const { - return m_faction->GetBaseSprite( false, 1, 0 ); +sprite::Sprite* Base::GetSprite() const { + uint8_t size = 0; + for ( uint8_t i = 0 ; i < s_base_render_population_thresholds.size() ; i++ ) { + if ( s_base_render_population_thresholds.at( i ) > m_population ) { + break; + } + size = i; + } + return m_faction->GetBaseSprite( m_tile->IsWater(), size, 0 ); // TODO: perimeter } void Base::Show() { if ( !m_render.is_rendered ) { const auto& c = m_render.coords; - Sprite* sprite = GetSprite(); + sprite::Sprite* sprite = GetSprite(); if ( !m_render.instance_id ) { m_render.instance_id = sprite->next_instance_id++; @@ -79,6 +97,14 @@ void Base::Show() { } ); + m_render.name_sprite->ShowAt( + { + m_render.coords.x, + m_render.coords.y - 0.25f, + m_render.coords.z - 0.25f + } + ); + m_render.is_rendered = true; } } @@ -86,6 +112,7 @@ void Base::Show() { void Base::Hide() { if ( m_render.is_rendered ) { GetSprite()->instanced_sprite->actor->RemoveInstance( m_render.instance_id ); + m_render.name_sprite->Hide(); m_render.is_rendered = false; } } @@ -94,7 +121,13 @@ const Base::render_data_t& Base::GetRenderData() const { return m_render_data; } -Base::meshtex_t Base::GetMeshTex( const InstancedSprite* sprite ) { +void Base::SetRenderCoords( const types::Vec3& coords ) { + Hide(); + m_render.coords = coords; + Show(); +} + +Base::meshtex_t Base::GetMeshTex( const sprite::InstancedSprite* sprite ) { auto* texture = sprite->actor->GetSpriteActor()->GetTexture(); NEWV( mesh, types::mesh::Rectangle ); mesh->SetCoords( diff --git a/src/task/game/base/Base.h b/src/task/game/base/Base.h index fd6d2a7f..bafa97f2 100644 --- a/src/task/game/base/Base.h +++ b/src/task/game/base/Base.h @@ -2,6 +2,8 @@ #include +#include "task/game/TileObject.h" + #include "game/unit/Types.h" #include "util/Timer.h" @@ -22,8 +24,15 @@ namespace game { class Game; class Slot; + +namespace sprite { class Sprite; class InstancedSprite; +} + +namespace text { +class InstancedText; +} namespace tile { class Tile; @@ -37,17 +46,17 @@ namespace base { class BaseManager; -class Base { +class Base : public TileObject { public: - // TODO: refactor Base( - BaseManager* bm, const size_t id, Slot* slot, tile::Tile* tile, + const bool is_owned, const types::Vec3& render_coords, - const bool is_owned + text::InstancedText* render_name_sprite, + size_t population ); ~Base(); @@ -55,7 +64,7 @@ class Base { const bool IsOwned() const; tile::Tile* GetTile() const; - Sprite* GetSprite() const; + sprite::Sprite* GetSprite() const; void Show(); void Hide(); @@ -69,26 +78,29 @@ class Base { }; const render_data_t& GetRenderData() const; -private: +protected: + void SetRenderCoords( const types::Vec3& coords ) override; - BaseManager* m_bm = nullptr; +private: size_t m_id = 0; faction::Faction* m_faction = nullptr; - tile::Tile* m_tile = nullptr; struct { types::Vec3 coords = {}; + text::InstancedText* name_sprite = nullptr; bool is_rendered = false; size_t instance_id = 0; } m_render; + size_t m_population = 0; + const bool m_is_owned = false; render_data_t m_render_data = {}; - meshtex_t GetMeshTex( const InstancedSprite* sprite ); + meshtex_t GetMeshTex( const sprite::InstancedSprite* sprite ); }; } diff --git a/src/task/game/base/BaseManager.cpp b/src/task/game/base/BaseManager.cpp index bce1ff7f..166d6c53 100644 --- a/src/task/game/base/BaseManager.cpp +++ b/src/task/game/base/BaseManager.cpp @@ -4,9 +4,15 @@ #include "Base.h" #include "task/game/Game.h" +#include "task/game/Slot.h" #include "task/game/unit/UnitManager.h" #include "task/game/tile/TileManager.h" +#include "task/game/text/InstancedTextManager.h" +#include "task/game/text/InstancedText.h" +#include "task/game/faction/Faction.h" #include "types/mesh/Rectangle.h" +#include "engine/Engine.h" +#include "loader/font/FontLoader.h" namespace task { namespace game { @@ -14,8 +20,7 @@ namespace base { BaseManager::BaseManager( Game* game ) : m_game( game ) - , m_ism( game->GetISM() ) - , m_slot_index( game->GetMySlotIndex() ) { + , m_name_font( game->GetITM()->GetInstancedFont( g_engine->GetFontLoader()->LoadFont( resource::TTF_ARIALN, 48 ) ) ) { } @@ -34,34 +39,35 @@ void BaseManager::SpawnBase( const size_t base_id, const size_t slot_index, const types::Vec2< size_t >& tile_coords, - const types::Vec3& render_coords + const types::Vec3& render_coords, + const ::game::base::BaseData& data ) { ASSERT( m_bases.find( base_id ) == m_bases.end(), "base id already exists" ); - auto* slot = m_game->GetSlot( slot_index ); auto* tile = m_game->GetTM()->GetTile( tile_coords ); + auto* slot = m_game->GetSlot( slot_index ); + auto* faction = slot->GetFaction(); - auto* base = m_bases.insert( + m_bases.insert( { base_id, new base::Base( - this, base_id, slot, tile, - { - render_coords.x, - render_coords.y, - render_coords.z - }, - slot_index == m_slot_index + slot_index == m_game->GetMySlotIndex(), + render_coords, + m_game->GetITM()->CreateInstancedText( + data.name, + m_name_font, + faction->m_colors.text, + faction->m_colors.text_shadow + ), + data.population ) } - ).first->second; - - types::mesh::Rectangle* mesh = nullptr; - types::texture::Texture* texture = nullptr; + ); m_game->RenderTile( tile, m_game->GetUM()->GetSelectedUnit() ); diff --git a/src/task/game/base/BaseManager.h b/src/task/game/base/BaseManager.h index fa42d255..f876dd11 100644 --- a/src/task/game/base/BaseManager.h +++ b/src/task/game/base/BaseManager.h @@ -5,6 +5,8 @@ #include "common/Common.h" +#include "game/base/BaseData.h" + #include "game/unit/Types.h" #include "types/Vec2.h" @@ -18,7 +20,17 @@ namespace task { namespace game { class Game; +class Slot; + +namespace sprite { class InstancedSpriteManager; +} + +namespace text { +class InstancedFont; +class InstancedText; +class InstancedTextManager; +} namespace tile { class Tile; @@ -43,16 +55,18 @@ CLASS( BaseManager, common::Class ) const size_t base_id, const size_t slot_index, const ::types::Vec2< size_t >& tile_coords, - const ::types::Vec3& render_coords + const ::types::Vec3& render_coords, + const ::game::base::BaseData& data ); // TODO void DespawnBase( const size_t base_id ); private: + friend class base::Base; - Game* m_game; - InstancedSpriteManager* m_ism; +private: - const size_t m_slot_index; + Game* m_game; + text::InstancedFont* m_name_font; std::unordered_map< size_t, base::Base* > m_bases = {}; diff --git a/src/task/game/faction/Faction.cpp b/src/task/game/faction/Faction.cpp index 436dce65..819de039 100644 --- a/src/task/game/faction/Faction.cpp +++ b/src/task/game/faction/Faction.cpp @@ -1,7 +1,7 @@ #include "Faction.h" #include "game/rules/Faction.h" -#include "task/game/InstancedSpriteManager.h" +#include "task/game/sprite/InstancedSpriteManager.h" #include "engine/Engine.h" #include "loader/texture/TextureLoader.h" @@ -11,10 +11,22 @@ namespace task { namespace game { namespace faction { -Faction::Faction( const ::game::rules::Faction* def, InstancedSpriteManager* ism ) +Faction::Faction( const ::game::rules::Faction* def, sprite::InstancedSpriteManager* ism ) : m_ism( ism ) , m_id( def->m_id ) - , m_border_color( def->m_colors.border ) + , m_colors( + { + def->m_colors.text, + def->m_colors.text_shadow, + def->m_colors.border + } + ) + , m_base_names( + { + def->m_base_names.land, + def->m_base_names.water, + } + ) , m_is_progenitor( def->m_flags & ::game::rules::Faction::FF_PROGENITOR ) , m_render( { @@ -24,7 +36,7 @@ Faction::Faction( const ::game::rules::Faction* def, InstancedSpriteManager* ism // } -Sprite* Faction::GetBaseSprite( const bool is_water, const uint8_t size, const uint8_t perimeter_level ) { +sprite::Sprite* Faction::GetBaseSprite( const bool is_water, const uint8_t size, const uint8_t perimeter_level ) { ASSERT_NOLOG( size < 4, "base size overflow ( " + std::to_string( size ) + " >= 4 )" ); ASSERT_NOLOG( perimeter_level < 3, "base perimeter level overflow ( " + std::to_string( perimeter_level ) + " >= 3 )" ); const uint8_t index = ( is_water @@ -77,7 +89,7 @@ Sprite* Faction::GetBaseSprite( const bool is_water, const uint8_t size, const u y + cxy.y }, dst_wh, - InstancedSpriteManager::ZL_BASES + ZL_BASES ), 1 } diff --git a/src/task/game/faction/Faction.h b/src/task/game/faction/Faction.h index 5617acf7..d7b2cb45 100644 --- a/src/task/game/faction/Faction.h +++ b/src/task/game/faction/Faction.h @@ -1,11 +1,13 @@ #pragma once #include +#include +#include #include "game/rules/Types.h" #include "types/Color.h" -#include "task/game/Sprite.h" +#include "task/game/sprite/Sprite.h" namespace types { namespace texture { @@ -20,24 +22,37 @@ class Faction; namespace task { namespace game { +namespace sprite { class InstancedSpriteManager; class InstancedSprite; +} namespace faction { class Faction { public: - Faction( const ::game::rules::Faction* def, InstancedSpriteManager* ism ); + Faction( const ::game::rules::Faction* def, sprite::InstancedSpriteManager* ism ); - Sprite* GetBaseSprite( const bool is_water, const uint8_t size, const uint8_t perimeter_level ); + sprite::Sprite* GetBaseSprite( const bool is_water, const uint8_t size, const uint8_t perimeter_level ); const std::string m_id; - const types::Color m_border_color; + + struct { + const types::Color text = {}; + const types::Color text_shadow = {}; + const types::Color border = {}; + } m_colors = {}; + + struct { + const std::vector< std::string > land = {}; + const std::vector< std::string > water = {}; + } m_base_names = {}; + const bool m_is_progenitor; private: - InstancedSpriteManager* m_ism = nullptr; + sprite::InstancedSpriteManager* m_ism = nullptr; struct { ::game::rules::bases_render_info_t bases_render; @@ -46,8 +61,8 @@ class Faction { types::texture::Texture* m_base_grid_texture = nullptr; types::texture::Texture* GetBaseGridTexture(); - std::unordered_map< uint8_t, Sprite > m_base_grid_sprites = {}; - Sprite m_base_sprites[6][4] = {}; + std::unordered_map< uint8_t, sprite::Sprite > m_base_grid_sprites = {}; + sprite::Sprite m_base_sprites[6][4] = {}; }; diff --git a/src/task/game/sprite/CMakeLists.txt b/src/task/game/sprite/CMakeLists.txt new file mode 100644 index 00000000..99a8dcdf --- /dev/null +++ b/src/task/game/sprite/CMakeLists.txt @@ -0,0 +1,5 @@ +SET( SRC ${SRC} + + ${PWD}/InstancedSpriteManager.cpp + + PARENT_SCOPE ) diff --git a/src/task/game/InstancedSprite.h b/src/task/game/sprite/InstancedSprite.h similarity index 94% rename from src/task/game/InstancedSprite.h rename to src/task/game/sprite/InstancedSprite.h index 81dd8210..7f9192ba 100644 --- a/src/task/game/InstancedSprite.h +++ b/src/task/game/sprite/InstancedSprite.h @@ -10,6 +10,7 @@ class Instanced; namespace task { namespace game { +namespace sprite { class InstancedSprite { public: @@ -23,3 +24,4 @@ class InstancedSprite { } } +} diff --git a/src/task/game/InstancedSpriteManager.cpp b/src/task/game/sprite/InstancedSpriteManager.cpp similarity index 92% rename from src/task/game/InstancedSpriteManager.cpp rename to src/task/game/sprite/InstancedSpriteManager.cpp index f49c03dd..77130cfb 100644 --- a/src/task/game/InstancedSpriteManager.cpp +++ b/src/task/game/sprite/InstancedSpriteManager.cpp @@ -1,6 +1,6 @@ #include "InstancedSpriteManager.h" -#include "task/game/InstancedSprite.h" +#include "InstancedSprite.h" #include "scene/Scene.h" #include "scene/actor/Instanced.h" #include "scene/actor/Sprite.h" @@ -8,26 +8,7 @@ namespace task { namespace game { - -static const float MAX_ZINDEX_ADJUSTMENT = 0.05f; -static const std::unordered_map< InstancedSpriteManager::z_level_t, float > s_zlevel_map = { - { - InstancedSpriteManager::ZL_TERRAIN, - 0.4f - }, - { - InstancedSpriteManager::ZL_BASES, - 0.5f - }, - { - InstancedSpriteManager::ZL_UNITS, - 0.6f - }, - { - InstancedSpriteManager::ZL_ANIMATIONS, - 0.7f - }, -}; +namespace sprite { InstancedSpriteManager::InstancedSpriteManager( scene::Scene* scene ) : m_scene( scene ) { @@ -199,3 +180,4 @@ types::texture::Texture* InstancedSpriteManager::GetRepaintedSourceTexture( cons } } +} diff --git a/src/task/game/InstancedSpriteManager.h b/src/task/game/sprite/InstancedSpriteManager.h similarity index 95% rename from src/task/game/InstancedSpriteManager.h rename to src/task/game/sprite/InstancedSpriteManager.h index 8923ea9f..fa7d0a46 100644 --- a/src/task/game/InstancedSpriteManager.h +++ b/src/task/game/sprite/InstancedSpriteManager.h @@ -5,6 +5,7 @@ #include "common/Common.h" +#include "task/game/Types.h" #include "game/map/Types.h" #include "types/texture/Types.h" #include "InstancedSprite.h" @@ -19,17 +20,11 @@ class Scene; namespace task { namespace game { +namespace sprite { CLASS( InstancedSpriteManager, ::common::Class ) public: - enum z_level_t { - ZL_TERRAIN, - ZL_BASES, - ZL_UNITS, - ZL_ANIMATIONS, - }; - InstancedSpriteManager( scene::Scene* scene ); ~InstancedSpriteManager(); @@ -62,3 +57,4 @@ CLASS( InstancedSpriteManager, ::common::Class ) } } +} diff --git a/src/task/game/Sprite.h b/src/task/game/sprite/Sprite.h similarity index 90% rename from src/task/game/Sprite.h rename to src/task/game/sprite/Sprite.h index ebaadcc3..5556393d 100644 --- a/src/task/game/Sprite.h +++ b/src/task/game/sprite/Sprite.h @@ -4,6 +4,7 @@ namespace task { namespace game { +namespace sprite { class InstancedSprite; @@ -15,3 +16,4 @@ class Sprite { } } +} diff --git a/src/task/game/text/CMakeLists.txt b/src/task/game/text/CMakeLists.txt new file mode 100644 index 00000000..9047afd0 --- /dev/null +++ b/src/task/game/text/CMakeLists.txt @@ -0,0 +1,7 @@ +SET( SRC ${SRC} + + ${PWD}/InstancedTextManager.cpp + ${PWD}/InstancedFont.cpp + ${PWD}/InstancedText.cpp + + PARENT_SCOPE ) diff --git a/src/task/game/text/InstancedFont.cpp b/src/task/game/text/InstancedFont.cpp new file mode 100644 index 00000000..127a7eb3 --- /dev/null +++ b/src/task/game/text/InstancedFont.cpp @@ -0,0 +1,263 @@ +#include "InstancedFont.h" + +#include + +#include "types/Font.h" +#include "types/texture/Texture.h" +#include "task/game/sprite/InstancedSpriteManager.h" + +namespace task { +namespace game { +namespace text { + +static const types::Vec2< float > s_font_scale = { + 0.003f, + 0.004f, +}; + +const std::vector< float > s_shadow_alpha_levels = { + 1.0f, + 1.0f, + 0.5f, + 0.25f, +}; + +InstancedFont::InstancedFont( sprite::InstancedSpriteManager* ism, const types::Font* font ) + : m_ism( ism ) + , m_font( font ) + , m_name( font->m_name ) { + + // load font into texture + + const uint8_t sym_offset = s_shadow_alpha_levels.size() * 2 + 1; // to keep antialiasing working + const uint8_t shadow_offset = s_shadow_alpha_levels.size(); + + unsigned int w = sym_offset + shadow_offset; + unsigned int h = 0; + for ( uint8_t i = 32 ; i < 128 ; i++ ) { + const auto& sym = m_font->m_symbols[ i ]; + w += sym.width + sym_offset; + h = std::max( h, sym.top + sym.height + sym_offset ); + } + + NEW( + m_base_texture, + types::texture::Texture, + "InstancedFont_" + m_name + "_BASE", + w + sym_offset + shadow_offset, + h + ( sym_offset + shadow_offset ) * 2 + ); + const auto f_paint_base_texture = [ this, sym_offset ]( const types::Vec2< uint8_t >& offsets, const types::Color& multiplier ) -> void { + unsigned int sym_x = sym_offset; + for ( uint8_t i = 32 ; i < 128 ; i++ ) { + const auto& sym = m_font->m_symbols[ i ]; + for ( size_t y = 0 ; y < sym.height ; y++ ) { + for ( size_t x = 0 ; x < sym.width ; x++ ) { + const auto alpha = sym.data[ y * sym.width + x ]; + if ( alpha ) { + const auto px = sym_x + x + offsets.x; + const auto py = sym_offset + y + offsets.y; + const auto c = types::Color::FromRGBA( m_base_texture->GetPixel( px, py ) ); + m_base_texture->SetPixel( + px, + py, + types::Color{ + std::fmax( c.value.red, multiplier.value.red ), + std::fmax( c.value.green, multiplier.value.green ), + std::fmax( c.value.blue, multiplier.value.blue ), + std::fmax( c.value.alpha, (float)alpha / 256.0f * multiplier.value.alpha ) + } + ); + } + } + } + sym_x += sym.width + sym_offset; + } + }; + for ( uint8_t i = 0 ; i < shadow_offset ; i++ ) { + const auto& c = types::Color{ + 0.0f, + 0.0f, + 0.0f, + s_shadow_alpha_levels.at( i ) + }; + const uint8_t before = shadow_offset - i - 1; + const uint8_t after = shadow_offset + i + 1; + std::vector< types::Vec2< uint8_t > > offsets = { + { + before, + shadow_offset, + }, + { + shadow_offset, + before, + }, + { + after, + shadow_offset, + }, + { + shadow_offset, + after, + }, + { + before, + before, + }, + { + after, + before, + }, + { + after, + after, + }, + { + before, + after, + }, + }; + for ( const auto& o : offsets ) { + f_paint_base_texture( o, c ); + } + } + f_paint_base_texture( + { + shadow_offset, + shadow_offset, + }, types::Color{ + 1.0f, + 1.0f, + 1.0f, + 1.0f + } + ); + unsigned int sym_x = sym_offset; + for ( uint8_t i = 32 ; i < 128 ; i++ ) { + const auto& sym = m_font->m_symbols[ i ]; + m_symbol_positions.insert( + { + i, + { + { + { + sym_x, + sym_offset + }, + { + sym.width + sym_offset, + sym.height + sym_offset + }, + { + sym_x,// + sym.width / 2, + sym_offset,// + sym.height / 2 + } + }, + { + { + sym.left, + sym.top + }, + { + sym.ax, + sym.ay + } + } + } + } + ); + sym_x += sym.width + sym_offset; + } + +} + +InstancedFont::~InstancedFont() { + for ( const auto& it : m_color_textures ) { + DELETE( it.second ); + } + DELETE( m_base_texture ); +} + +const std::string& InstancedFont::GetFontName() const { + return m_name; +} + +const std::vector< sprite::InstancedSprite* > InstancedFont::GetSymbolSprites( const std::string& text, const types::Color& color, const types::Color& shadow_color ) { + std::vector< sprite::InstancedSprite* > sprites = {}; + const auto texture_key = color.GetRGBA(); + auto texture_it = m_color_textures.find( texture_key ); + if ( texture_it == m_color_textures.end() ) { + NEWV( + texture, + types::texture::Texture, + "InstancedFont_" + m_name + "_" + std::to_string( texture_key ), + m_base_texture->m_width, + m_base_texture->m_height + ); + texture->ColorizeFrom( m_base_texture, color, shadow_color ); + texture_it = m_color_textures.insert( + { + texture_key, + texture, + } + ).first; + } + for ( const auto symbol : text ) { + ASSERT_NOLOG( m_symbol_positions.find( symbol ) != m_symbol_positions.end(), "invalid/unsupported symbol: " + std::to_string( symbol ) ); + const auto& pos = m_symbol_positions.find( symbol )->second; + sprites.push_back( + m_ism->GetInstancedSprite( + "InstancedFont_" + m_name + "_sym_" + std::to_string( symbol ), + texture_it->second, + pos.src.top_left, + pos.src.width_height, + pos.src.center, + { + (float)pos.src.width_height.x * s_font_scale.x, + (float)pos.src.width_height.y * s_font_scale.y + }, + ZL_TERRAIN_TEXT + ) + ); + } + return sprites; +} + +const std::vector< types::Vec2< float > > InstancedFont::GetSymbolOffsets( const std::string& text ) const { + std::vector< types::Vec2< float > > offsets = {}; + float total_offset = 0.0f; + float x_center = 0.0f; + const size_t middle = text.size() / 2 - 1; + for ( size_t i = 0 ; i < text.size() ; i++ ) { + const auto symbol = text.at( i ); + ASSERT_NOLOG( m_symbol_positions.find( symbol ) != m_symbol_positions.end(), "invalid/unsupported symbol: " + std::to_string( symbol ) ); + const auto& pos = m_symbol_positions.find( symbol )->second; + + const float ox = (float)pos.src.width_height.x + pos.dst.top_left.x; + const float oy = pos.dst.top_left.y;// - (float)pos.src.width_height.y / 2.0f; + + offsets.push_back( + { + total_offset + ox * s_font_scale.x, + oy * s_font_scale.y + } + ); + const float offset = ( pos.dst.advance.x + pos.dst.top_left.x ) * s_font_scale.x; + total_offset += offset; + if ( i < middle ) { + x_center += offset; + } + else if ( i == middle ) { + x_center += offset / 2.0f; + } + } + x_center = total_offset / 2.0f; + for ( auto& offset : offsets ) { + offset.x -= x_center; + } + return offsets; +} + +} +} +} diff --git a/src/task/game/text/InstancedFont.h b/src/task/game/text/InstancedFont.h new file mode 100644 index 00000000..58e3abbc --- /dev/null +++ b/src/task/game/text/InstancedFont.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include + +#include "game/map/Types.h" + +#include "types/Color.h" + +namespace types { +class Font; +namespace texture { +class Texture; +} +} + +namespace task { +namespace game { + +namespace sprite { +class InstancedSpriteManager; +class InstancedSprite; +} + +namespace text { + +class InstancedFont { +public: + InstancedFont( sprite::InstancedSpriteManager* ism, const types::Font* font ); + ~InstancedFont(); + + const std::string& GetFontName() const; + const std::vector< sprite::InstancedSprite* > GetSymbolSprites( const std::string& text, const types::Color& color, const types::Color& shadow_color ); + const std::vector< types::Vec2< float > > GetSymbolOffsets( const std::string& text ) const; + +private: + + struct symbol_pos_t { + struct { + ::game::map::pcx_texture_coordinates_t top_left; + ::game::map::pcx_texture_coordinates_t width_height; + ::game::map::pcx_texture_coordinates_t center; + } src; + struct { + types::Vec2< int > top_left; + types::Vec2< int > advance; + } dst; + }; + std::unordered_map< uint8_t, symbol_pos_t > m_symbol_positions = {}; + + sprite::InstancedSpriteManager* m_ism = nullptr; + const types::Font* m_font = nullptr; + const std::string m_name = ""; + + types::texture::Texture* m_base_texture = nullptr; + std::unordered_map< types::Color::rgba_t, types::texture::Texture* > m_color_textures = {}; +}; + +} +} +} diff --git a/src/task/game/text/InstancedText.cpp b/src/task/game/text/InstancedText.cpp new file mode 100644 index 00000000..345d02d6 --- /dev/null +++ b/src/task/game/text/InstancedText.cpp @@ -0,0 +1,50 @@ +#include "InstancedText.h" + +#include "InstancedFont.h" +#include "task/game/sprite/InstancedSprite.h" +#include "scene/actor/Instanced.h" + +namespace task { +namespace game { +namespace text { + +InstancedText::InstancedText( const std::string& text, InstancedFont* font, const types::Color& color, const types::Color& shadow_color ) + : m_text( text ) { + m_text_sprites = font->GetSymbolSprites( m_text, color, shadow_color ); + m_offsets = font->GetSymbolOffsets( m_text ); + m_instance_ids.reserve( m_text_sprites.size() ); +} + +InstancedText::~InstancedText() { + Hide(); +} + +void InstancedText::ShowAt( const types::Vec3& coords ) { + Hide(); + for ( size_t i = 0 ; i < m_text_sprites.size() ; i++ ) { + const auto& offsets = m_offsets.at( i ); + m_instance_ids.push_back( + m_text_sprites.at( i )->actor->AddInstance( + { + coords.x + offsets.x, + coords.y + offsets.y, + coords.z + } + ) + ); + } +} + +void InstancedText::Hide() { + if ( !m_instance_ids.empty() ) { + ASSERT_NOLOG( m_instance_ids.size() == m_text_sprites.size(), "instance ids size mismatch" ); + for ( size_t i = 0 ; i < m_instance_ids.size() ; i++ ) { + m_text_sprites.at( i )->actor->RemoveInstance( m_instance_ids.at( i ) ); + } + m_instance_ids.clear(); + } +} + +} +} +} \ No newline at end of file diff --git a/src/task/game/text/InstancedText.h b/src/task/game/text/InstancedText.h new file mode 100644 index 00000000..d12d8271 --- /dev/null +++ b/src/task/game/text/InstancedText.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "types/Vec3.h" +#include "types/Color.h" + +namespace task { +namespace game { + +namespace sprite { +class InstancedSprite; +class InstancedSpriteManager; +} + +namespace text { + +class InstancedFont; + +class InstancedText { +public: + InstancedText( const std::string& text, InstancedFont* font, const types::Color& color, const types::Color& shadow_color ); + ~InstancedText(); + + void ShowAt( const types::Vec3& coords ); + void Hide(); + +private: + const std::string m_text = ""; + std::vector< sprite::InstancedSprite* > m_text_sprites = {}; + std::vector< size_t > m_instance_ids = {}; + std::vector< types::Vec2< float > > m_offsets = {}; + +}; + +} +} +} diff --git a/src/task/game/text/InstancedTextManager.cpp b/src/task/game/text/InstancedTextManager.cpp new file mode 100644 index 00000000..ef8226c3 --- /dev/null +++ b/src/task/game/text/InstancedTextManager.cpp @@ -0,0 +1,51 @@ +#include "InstancedTextManager.h" + +#include "InstancedFont.h" +#include "InstancedText.h" +#include "types/Font.h" + +namespace task { +namespace game { +namespace text { + +InstancedTextManager::InstancedTextManager( sprite::InstancedSpriteManager* ism ) + : m_ism( ism ) { + // +} + +InstancedTextManager::~InstancedTextManager() { + for ( const auto& it : m_instanced_texts ) { + delete it.second; + } + for ( const auto& it : m_instanced_fonts ) { + delete it.second; + } +} + +InstancedFont* InstancedTextManager::GetInstancedFont( const types::Font* font ) { + auto it = m_instanced_fonts.find( font ); + if ( it == m_instanced_fonts.end() ) { + Log( "Initializing instanced font: " + font->m_name ); + it = m_instanced_fonts.insert( + { + font, + new InstancedFont( m_ism, font ) + } + ).first; + } + return it->second; +} + +InstancedText* InstancedTextManager::CreateInstancedText( + const std::string& text, + InstancedFont* font, + const types::Color& color, + const types::Color& shadow_color +) { + Log( "Creating instanced text: '" + text + "', " + font->GetFontName() + ", " + color.ToString() + "-" + shadow_color.ToString() ); + return new InstancedText( text, font, color, shadow_color ); +} + +} +} +} diff --git a/src/task/game/text/InstancedTextManager.h b/src/task/game/text/InstancedTextManager.h new file mode 100644 index 00000000..0c35035c --- /dev/null +++ b/src/task/game/text/InstancedTextManager.h @@ -0,0 +1,55 @@ +#pragma once + +#include "common/Common.h" + +#include "task/game/Types.h" + +#include "types/Color.h" + +namespace types { +class Font; +} + +namespace scene { +class Scene; +} + +namespace task { +namespace game { + +namespace sprite { +class InstancedSpriteManager; +} + +namespace text { + +class InstancedFont; +class InstancedText; + +CLASS( InstancedTextManager, common::Class ) +public: + + InstancedTextManager( sprite::InstancedSpriteManager* ism ); + ~InstancedTextManager(); + + InstancedFont* GetInstancedFont( const types::Font* font ); + + InstancedText* CreateInstancedText( + const std::string& text, + InstancedFont* font, + const types::Color& color, + const types::Color& shadow_color + ); + +private: + + sprite::InstancedSpriteManager* m_ism = nullptr; + + std::unordered_map< const types::Font*, InstancedFont* > m_instanced_fonts = {}; + std::unordered_map< const types::Font*, InstancedText* > m_instanced_texts = {}; + +}; + +} +} +} diff --git a/src/task/game/tile/Tile.cpp b/src/task/game/tile/Tile.cpp index ee13de19..d6eb61f4 100644 --- a/src/task/game/tile/Tile.cpp +++ b/src/task/game/tile/Tile.cpp @@ -35,6 +35,10 @@ Tile::Tile( const types::Vec2< size_t >& coords ) } +const bool Tile::IsWater() const { + return m_is_water; +} + const types::Vec2< size_t >& Tile::GetCoords() const { return m_coords; } @@ -98,11 +102,18 @@ void Tile::Render( size_t selected_unit_id ) { } m_render.currently_rendered_fake_badges.clear(); + bool should_show_units = !m_units.empty(); if ( m_base ) { m_base->Show(); + should_show_units = false; + for ( const auto& it : m_units ) { + if ( it.second->GetId() == selected_unit_id ) { + should_show_units = true; + break; + } + } } - - if ( !m_units.empty() ) { + if ( should_show_units ) { const auto units_order = GetUnitsOrder( m_units ); ASSERT_NOLOG( !units_order.empty(), "units order is empty" ); @@ -134,11 +145,14 @@ void Tile::Render( size_t selected_unit_id ) { } } if ( m_render.currently_rendered_unit ) { - m_render.currently_rendered_unit->Show(); const auto id = m_render.currently_rendered_unit->GetId(); + m_render.currently_rendered_unit->Show(); if ( id == most_important_unit_id ) { m_render.currently_rendered_unit->ShowBadge(); } + if ( id == selected_unit_id && m_render.currently_rendered_unit->IsActive() ) { + m_render.currently_rendered_unit->StartBadgeBlink(); + } } size_t idx; const auto& fake_badges = m_render.currently_rendered_fake_badges; @@ -147,6 +161,13 @@ void Tile::Render( size_t selected_unit_id ) { fake_badges.at( idx )->ShowFakeBadge( i ); } } + else { + for ( const auto& it : m_units ) { + it.second->Hide(); + it.second->HideFakeBadge(); + it.second->HideBadge(); + } + } } @@ -207,6 +228,8 @@ const Tile::render_data_t& Tile::GetRenderData() const { void Tile::Update( const ::game::map::tile::Tile& tile, const ::game::map::tile::TileState& ts ) { + m_is_water = tile.is_water_tile; + ::game::map::tile::tile_layer_type_t lt = ( tile.is_water_tile ? ::game::map::tile::LAYER_WATER : ::game::map::tile::LAYER_LAND @@ -481,6 +504,9 @@ void Tile::Update( const ::game::map::tile::Tile& tile, const ::game::map::tile: for ( const auto& it : m_units ) { it.second->UpdateFromTile(); } + if ( m_base ) { + m_base->UpdateFromTile(); + } } } diff --git a/src/task/game/tile/Tile.h b/src/task/game/tile/Tile.h index da665789..1c5aa582 100644 --- a/src/task/game/tile/Tile.h +++ b/src/task/game/tile/Tile.h @@ -38,6 +38,8 @@ class Tile { Tile( const types::Vec2< size_t >& coords ); + const bool IsWater() const; + const types::Vec2< size_t >& GetCoords() const; void AddUnit( unit::Unit* unit ); @@ -86,6 +88,8 @@ class Tile { std::vector< unit::Unit* > m_ordered_units = {}; bool m_is_units_reorder_needed = true; + bool m_is_water = false; + base::Base* m_base = nullptr; render_data_t m_render_data = {}; diff --git a/src/task/game/tile/TileManager.cpp b/src/task/game/tile/TileManager.cpp index cc15f1f4..e8ae036a 100644 --- a/src/task/game/tile/TileManager.cpp +++ b/src/task/game/tile/TileManager.cpp @@ -102,6 +102,13 @@ void TileManager::SelectTile( Tile* tile ) { m_selected_tile = tile; } +void TileManager::DeselectTile() { + if ( m_selected_tile ) { + m_selected_tile->Render(); + m_selected_tile = nullptr; + } +} + } } } diff --git a/src/task/game/tile/TileManager.h b/src/task/game/tile/TileManager.h index 65207dc2..325fbaa9 100644 --- a/src/task/game/tile/TileManager.h +++ b/src/task/game/tile/TileManager.h @@ -27,6 +27,7 @@ CLASS( TileManager, ::common::Class ) Tile* GetSelectedTile() const; void SelectTile( Tile* tile ); + void DeselectTile(); private: diff --git a/src/task/game/ui/bottom_bar/TilePreview.cpp b/src/task/game/ui/bottom_bar/TilePreview.cpp index aeec4d5d..a77f4e74 100644 --- a/src/task/game/ui/bottom_bar/TilePreview.cpp +++ b/src/task/game/ui/bottom_bar/TilePreview.cpp @@ -7,8 +7,8 @@ #include "types/mesh/Render.h" #include "ui/object/Label.h" #include "ui/object/Mesh.h" -#include "task/game/InstancedSpriteManager.h" -#include "task/game/InstancedSprite.h" +#include "task/game/sprite/InstancedSpriteManager.h" +#include "task/game/sprite/InstancedSprite.h" #include "scene/actor/Instanced.h" #include "scene/actor/Sprite.h" diff --git a/src/task/game/unit/BadgeDefs.cpp b/src/task/game/unit/BadgeDefs.cpp index cd80dded..9455af3f 100644 --- a/src/task/game/unit/BadgeDefs.cpp +++ b/src/task/game/unit/BadgeDefs.cpp @@ -3,9 +3,9 @@ #include "engine/Engine.h" #include "loader/texture/TextureLoader.h" #include "types/texture/Texture.h" -#include "task/game/InstancedSpriteManager.h" +#include "task/game/sprite/InstancedSpriteManager.h" #include "game/map/Consts.h" -#include "task/game/InstancedSprite.h" +#include "task/game/sprite/InstancedSprite.h" namespace task { namespace game { @@ -13,7 +13,7 @@ namespace unit { const BadgeDefs::consts_t BadgeDefs::s_consts = {}; -BadgeDefs::BadgeDefs( InstancedSpriteManager* ism ) +BadgeDefs::BadgeDefs( sprite::InstancedSpriteManager* ism ) : m_ism( ism ) { // } @@ -51,7 +51,7 @@ const types::Vec3 BadgeDefs::GetBadgeHealthbarCoords( const types::Vec3& unit_co }; } -InstancedSprite* BadgeDefs::GetBadgeSprite( const badge_type_t badge_type, const ::game::unit::morale_t morale ) { +sprite::InstancedSprite* BadgeDefs::GetBadgeSprite( const badge_type_t badge_type, const ::game::unit::morale_t morale ) { auto it1 = m_unitbadge_sprites.find( badge_type ); if ( it1 == m_unitbadge_sprites.end() ) { it1 = m_unitbadge_sprites.insert( @@ -102,7 +102,7 @@ InstancedSprite* BadgeDefs::GetBadgeSprite( const badge_type_t badge_type, const ::game::map::s_consts.tile.scale.x * s_consts.scale.x, ::game::map::s_consts.tile.scale.y * s_consts.scale.y * ::game::map::s_consts.sprite.y_scale }, - InstancedSpriteManager::ZL_UNITS, + ZL_UNITS, 0.008f ) } @@ -111,7 +111,7 @@ InstancedSprite* BadgeDefs::GetBadgeSprite( const badge_type_t badge_type, const return it2->second; } -InstancedSprite* BadgeDefs::GetFakeBadgeSprite() { +sprite::InstancedSprite* BadgeDefs::GetFakeBadgeSprite() { if ( !m_fake_badge ) { m_fake_badge = m_ism->GetInstancedSprite( "Badge_FAKE", @@ -132,14 +132,14 @@ InstancedSprite* BadgeDefs::GetFakeBadgeSprite() { ::game::map::s_consts.tile.scale.x * s_consts.fake_badges.scale.x, ::game::map::s_consts.tile.scale.y * s_consts.fake_badges.scale.y * ::game::map::s_consts.sprite.y_scale }, - InstancedSpriteManager::ZL_UNITS, + ZL_UNITS, 0.002f ); } return m_fake_badge; } -Sprite* BadgeDefs::GetBadgeHealthbarSprite( const float health ) { +sprite::Sprite* BadgeDefs::GetBadgeHealthbarSprite( const float health ) { const uint8_t step = round( health * ( s_consts.healthbars.resolution - 1 ) ); auto it = m_healthbar_sprites.find( step ); if ( it == m_healthbar_sprites.end() ) { @@ -195,7 +195,7 @@ Sprite* BadgeDefs::GetBadgeHealthbarSprite( const float health ) { ::game::map::s_consts.tile.scale.x * s_consts.healthbars.scale.x, ::game::map::s_consts.tile.scale.y * s_consts.healthbars.scale.y * ::game::map::s_consts.sprite.y_scale }, - InstancedSpriteManager::ZL_UNITS, + ZL_UNITS, 0.005f ), 1 diff --git a/src/task/game/unit/BadgeDefs.h b/src/task/game/unit/BadgeDefs.h index 626e1682..46927607 100644 --- a/src/task/game/unit/BadgeDefs.h +++ b/src/task/game/unit/BadgeDefs.h @@ -7,7 +7,7 @@ #include "game/unit/Types.h" -#include "task/game/Sprite.h" +#include "task/game/sprite/Sprite.h" namespace types::texture { class Texture; @@ -16,8 +16,10 @@ class Texture; namespace task { namespace game { +namespace sprite { class InstancedSprite; class InstancedSpriteManager; +} namespace unit { @@ -27,7 +29,7 @@ class BadgeDefs { static const types::Vec3 GetBadgeCoords( const types::Vec3& unit_coords ); static const types::Vec3 GetBadgeHealthbarCoords( const types::Vec3& unit_coords ); - BadgeDefs( InstancedSpriteManager* ism ); + BadgeDefs( sprite::InstancedSpriteManager* ism ); ~BadgeDefs(); typedef uint8_t badge_type_t; @@ -36,9 +38,9 @@ class BadgeDefs { static const badge_type_t BT_DEFAULT{ 1 << 1 }; static const badge_type_t BT_PROGENITOR{ 0 << 1 }; - InstancedSprite* GetBadgeSprite( const badge_type_t badge_type, const ::game::unit::morale_t morale ); - InstancedSprite* GetFakeBadgeSprite(); - Sprite* GetBadgeHealthbarSprite( const float health ); + sprite::InstancedSprite* GetBadgeSprite( const badge_type_t badge_type, const ::game::unit::morale_t morale ); + sprite::InstancedSprite* GetFakeBadgeSprite(); + sprite::Sprite* GetBadgeHealthbarSprite( const float health ); const size_t GetBadgeBlinkInterval() const; const types::Vec3 GetFakeBadgeCoords( const types::Vec3& coords, const uint8_t offset ) const; @@ -87,19 +89,19 @@ class BadgeDefs { } blink; } s_consts; - InstancedSpriteManager* const m_ism = nullptr; + sprite::InstancedSpriteManager* const m_ism = nullptr; - typedef std::unordered_map< ::game::unit::morale_t, InstancedSprite* > unitbadge_spritemap_t; + typedef std::unordered_map< ::game::unit::morale_t, sprite::InstancedSprite* > unitbadge_spritemap_t; typedef std::unordered_map< badge_type_t, unitbadge_spritemap_t > unitbadge_spritemaps_t; unitbadge_spritemaps_t m_unitbadge_sprites = {}; - InstancedSprite* m_fake_badge = nullptr; + sprite::InstancedSprite* m_fake_badge = nullptr; types::texture::Texture* m_badges_texture = nullptr; types::texture::Texture* GetBadgesTexture(); std::unordered_map< uint8_t, types::texture::Texture* > m_healthbar_textures = {}; - std::unordered_map< uint8_t, Sprite > m_healthbar_sprites = {}; + std::unordered_map< uint8_t, sprite::Sprite > m_healthbar_sprites = {}; }; diff --git a/src/task/game/unit/SlotBadges.cpp b/src/task/game/unit/SlotBadges.cpp index d95c6fd9..53cf0175 100644 --- a/src/task/game/unit/SlotBadges.cpp +++ b/src/task/game/unit/SlotBadges.cpp @@ -1,8 +1,8 @@ #include "SlotBadges.h" #include "BadgeDefs.h" -#include "task/game/InstancedSpriteManager.h" -#include "task/game/InstancedSprite.h" +#include "task/game/sprite/InstancedSpriteManager.h" +#include "task/game/sprite/InstancedSprite.h" #include "task/game/faction/Faction.h" #include "scene/actor/Instanced.h" @@ -12,7 +12,7 @@ namespace unit { SlotBadges::SlotBadges( BadgeDefs* badge_defs, - InstancedSpriteManager* ism, + sprite::InstancedSpriteManager* ism, const size_t slot_index, const faction::Faction* faction ) @@ -21,7 +21,7 @@ SlotBadges::SlotBadges( , m_badges_key( "Badge_" + std::to_string( slot_index ) ) , m_faction( faction ) { - const auto& c = faction->m_border_color.value; + const auto& c = faction->m_colors.border.value; m_repaint_rules = { { // active types::Color::RGB( 252, 0, 0 ), @@ -58,7 +58,7 @@ SlotBadges::~SlotBadges() { } } -Sprite* SlotBadges::GetUnitBadgeSprite( const ::game::unit::morale_t morale, const bool is_active ) { +sprite::Sprite* SlotBadges::GetUnitBadgeSprite( const ::game::unit::morale_t morale, const bool is_active ) { auto it = m_per_morale_sprites.find( morale ); if ( it == m_per_morale_sprites.end() ) { it = m_per_morale_sprites.insert( @@ -71,11 +71,11 @@ Sprite* SlotBadges::GetUnitBadgeSprite( const ::game::unit::morale_t morale, con } ).first; } - Sprite** sprite_ptr = is_active + sprite::Sprite** sprite_ptr = is_active ? &it->second.normal : &it->second.greyedout; if ( !*sprite_ptr ) { - NEW( *sprite_ptr, Sprite, { + NEW( *sprite_ptr, sprite::Sprite, { m_ism->GetRepaintedInstancedSprite( m_badges_key + "_" + std::to_string( morale ) + ( is_active ? "_NORMAL" @@ -109,9 +109,9 @@ void SlotBadges::HideFakeBadge( const size_t instance_id ) { GetFakeBadge()->instanced_sprite->actor->RemoveInstance( instance_id ); } -Sprite* SlotBadges::GetFakeBadge() { +sprite::Sprite* SlotBadges::GetFakeBadge() { if ( !m_fake_badge ) { - NEW( m_fake_badge, Sprite, { + NEW( m_fake_badge, sprite::Sprite, { m_ism->GetRepaintedInstancedSprite( m_badges_key + "_FAKE", m_badge_defs->GetFakeBadgeSprite(), diff --git a/src/task/game/unit/SlotBadges.h b/src/task/game/unit/SlotBadges.h index 0c42b6ed..fab4e465 100644 --- a/src/task/game/unit/SlotBadges.h +++ b/src/task/game/unit/SlotBadges.h @@ -11,8 +11,10 @@ namespace task { namespace game { +namespace sprite { class Sprite; class InstancedSpriteManager; +} namespace faction { class Faction; @@ -27,13 +29,13 @@ class SlotBadges { SlotBadges( BadgeDefs* badge_defs, - InstancedSpriteManager* ism, + sprite::InstancedSpriteManager* ism, const size_t slot_index, const faction::Faction* faction ); ~SlotBadges(); - Sprite* GetUnitBadgeSprite( const ::game::unit::morale_t morale, const bool is_active ); + sprite::Sprite* GetUnitBadgeSprite( const ::game::unit::morale_t morale, const bool is_active ); const size_t ShowFakeBadge( const types::Vec3& coords, const uint8_t offset ); void HideFakeBadge( const size_t instance_id ); @@ -41,22 +43,22 @@ class SlotBadges { private: BadgeDefs* const m_badge_defs; - InstancedSpriteManager* const m_ism; + sprite::InstancedSpriteManager* const m_ism; const std::string m_badges_key; const faction::Faction* m_faction = nullptr; class slot_sprites_t { public: - Sprite* normal; - Sprite* greyedout; + sprite::Sprite* normal; + sprite::Sprite* greyedout; }; std::unordered_map< ::game::unit::morale_t, slot_sprites_t > m_per_morale_sprites = {}; types::texture::repaint_rules_t m_repaint_rules = {}; types::texture::repaint_rules_t m_fake_badge_repaint_rules = {}; - Sprite* m_fake_badge = nullptr; - Sprite* GetFakeBadge(); + sprite::Sprite* m_fake_badge = nullptr; + sprite::Sprite* GetFakeBadge(); }; } diff --git a/src/task/game/unit/Unit.cpp b/src/task/game/unit/Unit.cpp index b8ff40bf..a9243a17 100644 --- a/src/task/game/unit/Unit.cpp +++ b/src/task/game/unit/Unit.cpp @@ -9,7 +9,7 @@ #include "BadgeDefs.h" #include "SlotBadges.h" #include "UnitManager.h" -#include "task/game/InstancedSprite.h" +#include "task/game/sprite/InstancedSprite.h" #include "scene/actor/Instanced.h" #include "types/mesh/Rectangle.h" #include "scene/actor/Sprite.h" @@ -33,12 +33,12 @@ Unit::Unit( const std::string& morale_string, const ::game::unit::health_t health ) - : m_um( um ) + : TileObject( tile ) + , m_um( um ) , m_badge_defs( badge_defs ) , m_id( id ) , m_def( def ) , m_slot_badges( m_um->GetSlotBadges( slot->GetIndex() ) ) - , m_tile( tile ) , m_render( { render_coords, @@ -106,15 +106,15 @@ const size_t Unit::GetSelectionWeight() const { return weight; } -Sprite* Unit::GetSprite() const { +sprite::Sprite* Unit::GetSprite() const { return m_def->GetSprite( m_morale ); } -Sprite* Unit::GetBadgeSprite() const { +sprite::Sprite* Unit::GetBadgeSprite() const { return m_render.badge.def; } -Sprite* Unit::GetBadgeHealthbarSprite() const { +sprite::Sprite* Unit::GetBadgeHealthbarSprite() const { return m_render.badge.healthbar.def; } @@ -333,11 +333,6 @@ void Unit::MoveToTile( tile::Tile* dst_tile ) { m_mover.Scroll( from, m_um->GetCloserCoords( to, from ), MOVE_DURATION_MS ); } -void Unit::UpdateFromTile() { - ASSERT_NOLOG( m_tile, "tile not set" ); - SetRenderCoords( m_tile->GetRenderData().coords.InvertY() ); -} - const bool Unit::IsMoving() const { return m_mover.IsRunning(); } @@ -350,7 +345,7 @@ const bool Unit::ShouldBeActive() const { return m_is_owned && CanMove(); } -Unit::meshtex_t Unit::GetMeshTex( const InstancedSprite* sprite ) { +Unit::meshtex_t Unit::GetMeshTex( const sprite::InstancedSprite* sprite ) { auto* texture = sprite->actor->GetSpriteActor()->GetTexture(); NEWV( mesh, types::mesh::Rectangle ); mesh->SetCoords( diff --git a/src/task/game/unit/Unit.h b/src/task/game/unit/Unit.h index 72d6cc61..d2aada6b 100644 --- a/src/task/game/unit/Unit.h +++ b/src/task/game/unit/Unit.h @@ -2,6 +2,8 @@ #include +#include "task/game/TileObject.h" + #include "game/unit/Types.h" #include "util/Timer.h" @@ -22,8 +24,11 @@ namespace game { class Game; class Slot; + +namespace sprite { class Sprite; class InstancedSprite; +} namespace tile { class Tile; @@ -36,12 +41,11 @@ class BadgeDefs; class SlotBadges; class UnitManager; -class Unit { +class Unit : public TileObject { public: static constexpr size_t MOVE_DURATION_MS = 125; - // TODO: refactor Unit( UnitManager* um, BadgeDefs* badge_defs, @@ -65,9 +69,9 @@ class Unit { const size_t GetSelectionWeight() const; - Sprite* GetSprite() const; - Sprite* GetBadgeSprite() const; - Sprite* GetBadgeHealthbarSprite() const; + sprite::Sprite* GetSprite() const; + sprite::Sprite* GetBadgeSprite() const; + sprite::Sprite* GetBadgeHealthbarSprite() const; const std::string GetNameString() const; const std::string GetStatsString() const; @@ -99,9 +103,7 @@ class Unit { void SetTile( tile::Tile* dst_tile ); void MoveToTile( tile::Tile* dst_tile ); - void UpdateFromTile(); - const bool IsValid() const; const bool IsMoving() const; struct meshtex_t { @@ -115,6 +117,9 @@ class Unit { }; const render_data_t& GetRenderData() const; +protected: + void SetRenderCoords( const types::Vec3& coords ) override; + private: UnitManager* m_um = nullptr; @@ -124,16 +129,15 @@ class Unit { size_t m_id = 0; UnitDef* m_def = nullptr; - tile::Tile* m_tile = nullptr; struct { types::Vec3 coords = {}; bool is_rendered = false; size_t instance_id = 0; struct { - Sprite* def = nullptr; + sprite::Sprite* def = nullptr; size_t instance_id = 0; struct { - Sprite* def = nullptr; + sprite::Sprite* def = nullptr; size_t instance_id = 0; } healthbar; struct { @@ -161,8 +165,7 @@ class Unit { util::Scroller< types::Vec3 > m_mover; - Unit::meshtex_t GetMeshTex( const InstancedSprite* sprite ); - void SetRenderCoords( const types::Vec3& coords ); + Unit::meshtex_t GetMeshTex( const sprite::InstancedSprite* sprite ); }; } diff --git a/src/task/game/unit/UnitDef.cpp b/src/task/game/unit/UnitDef.cpp index 293295d3..063aeaaf 100644 --- a/src/task/game/unit/UnitDef.cpp +++ b/src/task/game/unit/UnitDef.cpp @@ -5,14 +5,14 @@ #include "engine/Engine.h" #include "loader/texture/TextureLoader.h" #include "util/String.h" -#include "task/game/InstancedSpriteManager.h" +#include "task/game/sprite/InstancedSpriteManager.h" #include "game/map/Consts.h" namespace task { namespace game { namespace unit { -UnitDef::UnitDef( InstancedSpriteManager* ism, const ::game::unit::Def* unitdef ) +UnitDef::UnitDef( sprite::InstancedSpriteManager* ism, const ::game::unit::Def* unitdef ) : m_ism( ism ) , m_id( unitdef->m_id ) , m_name( unitdef->m_name ) @@ -45,7 +45,7 @@ UnitDef::UnitDef( InstancedSpriteManager* ism, const ::game::unit::Def* unitdef UnitDef::~UnitDef() { if ( m_type == ::game::unit::DT_STATIC ) { - if ( static_.render.morale_based_xshift ) { + if ( m_render.morale_based_xshift ) { if ( static_.render.morale_based_sprites ) { DELETE( static_.render.morale_based_sprites ); } @@ -57,7 +57,7 @@ const bool UnitDef::IsArtillery() const { return m_id != "SporeLauncher"; } -Sprite* UnitDef::GetSprite( const ::game::unit::morale_t morale ) { +sprite::Sprite* UnitDef::GetSprite( const ::game::unit::morale_t morale ) { ASSERT_NOLOG( m_type == ::game::unit::DT_STATIC, "only static units are supported for now" ); ASSERT_NOLOG( static_.render.is_sprite, "only sprite unitdefs are supported for now" ); @@ -67,7 +67,7 @@ Sprite* UnitDef::GetSprite( const ::game::unit::morale_t morale ) { } auto it = static_.render.morale_based_sprites->find( morale ); if ( it == static_.render.morale_based_sprites->end() ) { - const uint32_t xshift = static_.render.morale_based_xshift * ( morale - ::game::unit::MORALE_MIN ); + const uint32_t xshift = m_render.morale_based_xshift * ( morale - ::game::unit::MORALE_MIN ); it = static_.render.morale_based_sprites->insert( { morale, @@ -89,7 +89,7 @@ Sprite* UnitDef::GetSprite( const ::game::unit::morale_t morale ) { ::game::map::s_consts.tile.scale.x, ::game::map::s_consts.tile.scale.y * ::game::map::s_consts.sprite.y_scale }, - InstancedSpriteManager::ZL_UNITS + ZL_UNITS ), } } @@ -117,7 +117,7 @@ Sprite* UnitDef::GetSprite( const ::game::unit::morale_t morale ) { ::game::map::s_consts.tile.scale.x, ::game::map::s_consts.tile.scale.y * ::game::map::s_consts.sprite.y_scale }, - InstancedSpriteManager::ZL_UNITS + ZL_UNITS ), 1 }; diff --git a/src/task/game/unit/UnitDef.h b/src/task/game/unit/UnitDef.h index 0486b24d..d85dd6ee 100644 --- a/src/task/game/unit/UnitDef.h +++ b/src/task/game/unit/UnitDef.h @@ -7,7 +7,7 @@ #include "types/Vec3.h" // TODO: remove? -#include "task/game/Sprite.h" +#include "task/game/sprite/Sprite.h" namespace types { namespace texture { @@ -22,18 +22,20 @@ class Def; namespace task { namespace game { +namespace sprite { class InstancedSpriteManager; +} namespace unit { class UnitDef { public: - UnitDef( InstancedSpriteManager* ism, const ::game::unit::Def* unitdef ); + UnitDef( sprite::InstancedSpriteManager* ism, const ::game::unit::Def* unitdef ); ~UnitDef(); const bool IsArtillery() const; - Sprite* GetSprite( const ::game::unit::morale_t morale ); + sprite::Sprite* GetSprite( const ::game::unit::morale_t morale ); const bool IsImmovable() const; @@ -42,7 +44,7 @@ class UnitDef { private: - InstancedSpriteManager* const m_ism; + sprite::InstancedSpriteManager* const m_ism; ::game::unit::sprite_render_info_t m_render = {}; @@ -50,16 +52,15 @@ class UnitDef { std::string m_name; ::game::unit::def_type_t m_type; - typedef std::unordered_map< ::game::unit::morale_t, Sprite > morale_based_sprites_t; + typedef std::unordered_map< ::game::unit::morale_t, sprite::Sprite > morale_based_sprites_t; struct { ::game::unit::movement_type_t movement_type; ::game::unit::movement_t movement_per_turn; struct { bool is_sprite = false; - uint32_t morale_based_xshift = 0; types::texture::Texture* texture = nullptr; - Sprite sprite = {}; + sprite::Sprite sprite = {}; morale_based_sprites_t* morale_based_sprites = nullptr; } render = {}; } static_ = {}; diff --git a/src/task/game/unit/UnitManager.h b/src/task/game/unit/UnitManager.h index cd84a2d8..f8daef94 100644 --- a/src/task/game/unit/UnitManager.h +++ b/src/task/game/unit/UnitManager.h @@ -18,7 +18,10 @@ namespace task { namespace game { class Game; + +namespace sprite { class InstancedSpriteManager; +} namespace tile { class Tile; @@ -79,7 +82,7 @@ CLASS( UnitManager, common::Class ) private: Game* m_game; - InstancedSpriteManager* m_ism; + sprite::InstancedSpriteManager* m_ism; const size_t m_slot_index; diff --git a/src/types/Color.cpp b/src/types/Color.cpp index 073dcfaa..db4c85ee 100644 --- a/src/types/Color.cpp +++ b/src/types/Color.cpp @@ -73,6 +73,15 @@ const Color Color::operator/( float operand ) const { }; } +const Color Color::operator*( const Color& other ) const { + return { + value.red * other.value.red, + value.green * other.value.green, + value.blue * other.value.blue, + value.alpha * other.value.alpha, + }; +} + const Color::rgba_t Color::GetRGBA() const { return RGBA( value.red * 255, value.green * 255, value.blue * 255, value.alpha * 255 ); }; diff --git a/src/types/Color.h b/src/types/Color.h index b5357532..b624a3ff 100644 --- a/src/types/Color.h +++ b/src/types/Color.h @@ -42,6 +42,8 @@ class Color { const Color operator*( const float operand ) const; const Color operator/( const float operand ) const; + const Color operator*( const Color& other ) const; + const rgba_t GetRGBA() const; static Color FromRGBA( const rgba_t rgba ); static Color FromRGBA( const uint8_t red, const uint8_t green, const uint8_t blue, const uint8_t alpha = 255 ); diff --git a/src/types/Font.cpp b/src/types/Font.cpp index 5bf4f075..3e13e971 100644 --- a/src/types/Font.cpp +++ b/src/types/Font.cpp @@ -1,3 +1,4 @@ +#include #include #include "Font.h" @@ -6,6 +7,11 @@ namespace types { +Font::Font( const std::string& name ) + : m_name( name ) { + // +} + Font::~Font() { if ( m_graphics_object ) { m_graphics_object->Remove(); diff --git a/src/types/Font.h b/src/types/Font.h index bfff9b79..6725a4a2 100644 --- a/src/types/Font.h +++ b/src/types/Font.h @@ -11,8 +11,11 @@ class ObjectLink; namespace types { CLASS( Font, common::Class ) + Font( const std::string& name ); virtual ~Font(); + const std::string m_name = ""; + struct dimensions_t { float width; float height; @@ -28,7 +31,7 @@ CLASS( Font, common::Class ) unsigned char* data; }; - std::string m_name = ""; + std::string m_filename = ""; bitmap_t m_symbols[128] = {}; dimensions_t m_dimensions = { 0.0, diff --git a/src/types/texture/Texture.cpp b/src/types/texture/Texture.cpp index 8f6009c0..2e904f70 100644 --- a/src/types/texture/Texture.cpp +++ b/src/types/texture/Texture.cpp @@ -746,6 +746,33 @@ void Texture::RepaintFrom( const types::texture::Texture* original, const repain } +void Texture::ColorizeFrom( const types::texture::Texture* original, const types::Color& color, const types::Color& shadow_color ) { + ASSERT( m_width == original->m_width, "repaint width mismatch" ); + ASSERT( m_height == original->m_height, "repaint width mismatch" ); + ASSERT( m_bpp == original->m_bpp, "repaint bpp mismatch" ); + ASSERT( m_bitmap, "bitmap not set" ); + ASSERT( original->m_bitmap, "original bitmap not set" ); + + uint32_t rgba; + repaint_rules_t::const_iterator rule_it; + for ( size_t y = 0 ; y < m_height ; y++ ) { + for ( size_t x = 0 ; x < m_width ; x++ ) { + const auto idx = ( y * m_width + x ) * m_bpp; + memcpy( &rgba, ptr( original->m_bitmap, idx, m_bpp ), m_bpp ); + if ( rgba ) { + const auto c = types::Color::FromRGBA( rgba ); + if ( !c.value.red && !c.value.green && !c.value.blue ) { + rgba = ( c * shadow_color ).GetRGBA(); + } + else { + rgba = ( c * color ).GetRGBA(); + } + memcpy( ptr( m_bitmap, idx, m_bpp ), &rgba, m_bpp ); + } + } + } +} + void Texture::Rotate() { unsigned char* new_bitmap = (unsigned char*)malloc( m_bitmap_size ); diff --git a/src/types/texture/Texture.h b/src/types/texture/Texture.h index 7d2cdad0..6977f5be 100644 --- a/src/types/texture/Texture.h +++ b/src/types/texture/Texture.h @@ -74,6 +74,7 @@ CLASS( Texture, Serializable ) void Fill( const size_t x1, const size_t y1, const size_t x2, const size_t y2, const types::Color& color ); void RepaintFrom( const types::texture::Texture* original, const repaint_rules_t& rules ); + void ColorizeFrom( const types::texture::Texture* original, const types::Color& color, const types::Color& shadow_color ); void Rotate(); void FlipV(); diff --git a/src/util/String.cpp b/src/util/String.cpp index 8265de57..486ce0b5 100644 --- a/src/util/String.cpp +++ b/src/util/String.cpp @@ -10,6 +10,9 @@ const std::vector< std::string > String::SplitToLines( const std::string& input std::vector< std::string > lines = {}; auto ss = std::stringstream{ input }; for ( std::string line ; std::getline( ss, line, '\n' ) ; ) { + if ( line.back() == '\r' ) { + line.erase( line.end() - 1 ); + } lines.push_back( line ); } return lines;