Compare commits
12 Commits
5407f66c9e
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 817c8fc8a0 | |||
| 3fe8fa9b32 | |||
| 65f710bf7a | |||
| 72302554ae | |||
| 03530d0439 | |||
| 705d32e919 | |||
| e420db2896 | |||
| 785700f819 | |||
| 07863577bc | |||
| 8a341be027 | |||
| 93fb914e54 | |||
| 8d659c44e5 |
+1
-1
@@ -69,7 +69,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE SDL3::SDL3)
|
|||||||
if(EXTERNAL_SOURCES)
|
if(EXTERNAL_SOURCES)
|
||||||
set_source_files_properties(
|
set_source_files_properties(
|
||||||
${EXTERNAL_SOURCES}
|
${EXTERNAL_SOURCES}
|
||||||
PROPERTIES COMPILE_OPTIONS "-Wno-missing-field-initializers;-Wno-deprecated-declarations"
|
PROPERTIES COMPILE_OPTIONS "-Wno-missing-field-initializers;-Wno-deprecated-declarations;-Wno-tautological-compare"
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ _macos-release:
|
|||||||
|
|
||||||
# Compila la versió Apple Silicon
|
# Compila la versió Apple Silicon
|
||||||
@cmake -S . -B $(BUILDDIR)/arm $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release \
|
@cmake -S . -B $(BUILDDIR)/arm $(CMAKE_GEN) -DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
-DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=13.3 \
|
||||||
-DMACOS_BUNDLE=ON $(CMAKE_DEFS)
|
-DMACOS_BUNDLE=ON $(CMAKE_DEFS)
|
||||||
@cmake --build $(BUILDDIR)/arm -j$(JOBS)
|
@cmake --build $(BUILDDIR)/arm -j$(JOBS)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ notification:
|
|||||||
antialias_off: "AA INACTIU"
|
antialias_off: "AA INACTIU"
|
||||||
postfx_on: "POSTPROCESSAT ACTIU"
|
postfx_on: "POSTPROCESSAT ACTIU"
|
||||||
postfx_off: "POSTPROCESSAT INACTIU"
|
postfx_off: "POSTPROCESSAT INACTIU"
|
||||||
|
screenshot: "IMATGE {file} GUARDADA A {folder}"
|
||||||
locale_switched: "IDIOMA: {lang}"
|
locale_switched: "IDIOMA: {lang}"
|
||||||
gamepad_connected: "{name} CONNECTAT"
|
gamepad_connected: "{name} CONNECTAT"
|
||||||
gamepad_disconnected: "{name} DESCONNECTAT"
|
gamepad_disconnected: "{name} DESCONNECTAT"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ notification:
|
|||||||
antialias_off: "AA OFF"
|
antialias_off: "AA OFF"
|
||||||
postfx_on: "POSTPROCESS ON"
|
postfx_on: "POSTPROCESS ON"
|
||||||
postfx_off: "POSTPROCESS OFF"
|
postfx_off: "POSTPROCESS OFF"
|
||||||
|
screenshot: "IMAGE {file} SAVED AT {folder}"
|
||||||
locale_switched: "LANGUAGE: {lang}"
|
locale_switched: "LANGUAGE: {lang}"
|
||||||
gamepad_connected: "{name} CONNECTED"
|
gamepad_connected: "{name} CONNECTED"
|
||||||
gamepad_disconnected: "{name} DISCONNECTED"
|
gamepad_disconnected: "{name} DISCONNECTED"
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# char_underscore.shp - Símbolo _ (barra baja)
|
||||||
|
# Dimensiones: 20×40 (blocky display)
|
||||||
|
|
||||||
|
name: char_underscore
|
||||||
|
scale: 1.0
|
||||||
|
center: 10, 20
|
||||||
|
|
||||||
|
# Línea horizontal abajo (bajo la baseline de las letras)
|
||||||
|
line: 3,33 17,33
|
||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 361 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 537 KiB |
@@ -29,7 +29,7 @@
|
|||||||
<key>CSResourcesFileMapped</key>
|
<key>CSResourcesFileMapped</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>10.15</string>
|
<string>13.3</string>
|
||||||
<key>NSHighResolutionCapable</key>
|
<key>NSHighResolutionCapable</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
|||||||
@@ -164,12 +164,16 @@ namespace Defaults::Title {
|
|||||||
// alpha = 255 (sentinela "color vàlid") fa que el pipeline ignori
|
// alpha = 255 (sentinela "color vàlid") fa que el pipeline ignori
|
||||||
// el color global de l'oscil·lador per a aquesta crida.
|
// el color global de l'oscil·lador per a aquesta crida.
|
||||||
namespace Colors {
|
namespace Colors {
|
||||||
|
// Ambre neon: el mateix to dels missatges d'inici/fi de fase
|
||||||
|
// (STAGE_MESSAGE_COLOR a game_scene.cpp) per unificar el feel.
|
||||||
|
constexpr SDL_Color AMBER = {.r = 255, .g = 200, .b = 70, .a = 255};
|
||||||
|
|
||||||
constexpr SDL_Color STARFIELD = {.r = 200, .g = 220, .b = 255, .a = 255}; // Blanc-blau gel
|
constexpr SDL_Color STARFIELD = {.r = 200, .g = 220, .b = 255, .a = 255}; // Blanc-blau gel
|
||||||
constexpr SDL_Color LOGO_MAIN = {.r = 0, .g = 255, .b = 255, .a = 255}; // Cian pur
|
constexpr SDL_Color LOGO_MAIN = {.r = 0, .g = 255, .b = 255, .a = 255}; // Cian pur
|
||||||
constexpr SDL_Color LOGO_SHADOW = STARFIELD; // Color de l'starfield (offset)
|
constexpr SDL_Color LOGO_SHADOW = STARFIELD; // Color de l'starfield (offset)
|
||||||
constexpr SDL_Color SHIP_P1 = {.r = 255, .g = 255, .b = 255, .a = 255}; // Blanc
|
constexpr SDL_Color SHIP_P1 = {.r = 255, .g = 255, .b = 255, .a = 255}; // Blanc
|
||||||
constexpr SDL_Color SHIP_P2 = {.r = 255, .g = 255, .b = 255, .a = 255}; // Blanc
|
constexpr SDL_Color SHIP_P2 = {.r = 255, .g = 255, .b = 255, .a = 255}; // Blanc
|
||||||
constexpr SDL_Color PRESS_START = {.r = 255, .g = 255, .b = 255, .a = 255}; // Blanc
|
constexpr SDL_Color PRESS_START = AMBER; // Ambre (com les frases de fase)
|
||||||
constexpr SDL_Color JAILGAMES_LOGO = {.r = 0, .g = 255, .b = 255, .a = 255}; // Cian pur
|
constexpr SDL_Color JAILGAMES_LOGO = {.r = 0, .g = 255, .b = 255, .a = 255}; // Cian pur
|
||||||
constexpr SDL_Color COPYRIGHT = {.r = 0, .g = 255, .b = 255, .a = 255}; // Mateix cian (el brillo es baixa al render: COPYRIGHT_BRIGHTNESS)
|
constexpr SDL_Color COPYRIGHT = {.r = 0, .g = 255, .b = 255, .a = 255}; // Mateix cian (el brillo es baixa al render: COPYRIGHT_BRIGHTNESS)
|
||||||
} // namespace Colors
|
} // namespace Colors
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace Graphics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cargar símbolos
|
// Cargar símbolos
|
||||||
const std::string SYMBOLS[] = {".", ",", "-", ":", "!", "?", "/", "(", ")"};
|
const std::string SYMBOLS[] = {".", ",", "-", ":", "!", "?", "/", "(", ")", "_"};
|
||||||
for (const auto& sym : SYMBOLS) {
|
for (const auto& sym : SYMBOLS) {
|
||||||
char c = sym[0];
|
char c = sym[0];
|
||||||
std::string filename = getShapeFilename(c);
|
std::string filename = getShapeFilename(c);
|
||||||
@@ -171,6 +171,8 @@ namespace Graphics {
|
|||||||
return "font/char_lparen.shp";
|
return "font/char_lparen.shp";
|
||||||
case ')':
|
case ')':
|
||||||
return "font/char_rparen.shp";
|
return "font/char_rparen.shp";
|
||||||
|
case '_':
|
||||||
|
return "font/char_underscore.shp";
|
||||||
case ' ':
|
case ' ':
|
||||||
return ""; // Espai es maneja sin load shape
|
return ""; // Espai es maneja sin load shape
|
||||||
|
|
||||||
@@ -183,6 +185,10 @@ namespace Graphics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto VectorText::isSupported(char c) const -> bool {
|
auto VectorText::isSupported(char c) const -> bool {
|
||||||
|
// Mateix fallback que render(): a-z es resol al glif A-Z.
|
||||||
|
if (c >= 'a' && c <= 'z') {
|
||||||
|
c -= 32;
|
||||||
|
}
|
||||||
return chars_.contains(c);
|
return chars_.contains(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +227,14 @@ namespace Graphics {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback de la font (només tenim glifs en majúscula): tractem
|
||||||
|
// les minúscules a-z com les seves majúscules A-Z. Mentre no hi
|
||||||
|
// haja glifs de minúscula, això evita que el text en minúscules
|
||||||
|
// (p. ex. rutes de fitxer) desaparega.
|
||||||
|
if (c >= 'a' && c <= 'z') {
|
||||||
|
c -= 32;
|
||||||
|
}
|
||||||
|
|
||||||
// Verificar si el carácter está soportado
|
// Verificar si el carácter está soportado
|
||||||
auto it = chars_.find(c);
|
auto it = chars_.find(c);
|
||||||
if (it != chars_.end()) {
|
if (it != chars_.end()) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -132,6 +133,7 @@ namespace Rendering::GPU {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GpuFrameRenderer::destroy() {
|
void GpuFrameRenderer::destroy() {
|
||||||
|
destroyCaptureResources();
|
||||||
destroyOffscreen();
|
destroyOffscreen();
|
||||||
postfx_pipeline_.destroy();
|
postfx_pipeline_.destroy();
|
||||||
bloom_pipeline_.destroy();
|
bloom_pipeline_.destroy();
|
||||||
@@ -172,7 +174,7 @@ namespace Rendering::GPU {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SDL_WaitAndAcquireGPUSwapchainTexture(cmd_buffer_, device_.window(), &swapchain_texture_, nullptr, nullptr)) {
|
if (!SDL_WaitAndAcquireGPUSwapchainTexture(cmd_buffer_, device_.window(), &swapchain_texture_, &swapchain_w_, &swapchain_h_)) {
|
||||||
std::cerr << "[GpuFrameRenderer] WaitAndAcquire: " << SDL_GetError() << '\n';
|
std::cerr << "[GpuFrameRenderer] WaitAndAcquire: " << SDL_GetError() << '\n';
|
||||||
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||||
cmd_buffer_ = nullptr;
|
cmd_buffer_ = nullptr;
|
||||||
@@ -540,11 +542,20 @@ namespace Rendering::GPU {
|
|||||||
SDL_EndGPURenderPass(render_pass_);
|
SDL_EndGPURenderPass(render_pass_);
|
||||||
render_pass_ = nullptr;
|
render_pass_ = nullptr;
|
||||||
}
|
}
|
||||||
|
compositeTo(swapchain_texture_);
|
||||||
|
}
|
||||||
|
|
||||||
// Pase final: render pass sobre SWAPCHAIN con clear a negro (cubre el
|
void GpuFrameRenderer::compositeTo(SDL_GPUTexture* target_tex) {
|
||||||
|
// Cos comú del pase de postpro: sample offscreen + bloom → target_tex
|
||||||
|
// (swapchain en el camí normal, o la textura de captura per al
|
||||||
|
// screenshot). El caller s'encarrega de tancar qualsevol render pass
|
||||||
|
// previ. Com que la textura de captura té la mateixa mida que la
|
||||||
|
// swapchain, el mateix viewport/letterbox produeix píxels idèntics.
|
||||||
|
|
||||||
|
// Pase final: render pass sobre el target con clear a negro (cubre el
|
||||||
// letterbox del viewport físico).
|
// letterbox del viewport físico).
|
||||||
SDL_GPUColorTargetInfo target{};
|
SDL_GPUColorTargetInfo target{};
|
||||||
target.texture = swapchain_texture_;
|
target.texture = target_tex;
|
||||||
target.clear_color = SDL_FColor{.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
target.clear_color = SDL_FColor{.r = 0.0F, .g = 0.0F, .b = 0.0F, .a = 1.0F};
|
||||||
target.load_op = SDL_GPU_LOADOP_CLEAR;
|
target.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||||
target.store_op = SDL_GPU_STOREOP_STORE;
|
target.store_op = SDL_GPU_STOREOP_STORE;
|
||||||
@@ -664,14 +675,150 @@ namespace Rendering::GPU {
|
|||||||
}
|
}
|
||||||
flushBatch();
|
flushBatch();
|
||||||
bloomPass();
|
bloomPass();
|
||||||
compositePass();
|
compositePass(); // → swapchain (camí de presentació normal)
|
||||||
if (render_pass_ != nullptr) {
|
if (render_pass_ != nullptr) {
|
||||||
SDL_EndGPURenderPass(render_pass_);
|
SDL_EndGPURenderPass(render_pass_);
|
||||||
render_pass_ = nullptr;
|
render_pass_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Captura (screenshot): segon composite sobre la textura pròpia +
|
||||||
|
// readback a CPU. Només quan hi ha petició pendent i els recursos es
|
||||||
|
// poden crear; si no, submit normal i prou. La petició es consumeix
|
||||||
|
// sempre (s'haja pogut capturar o no) per no quedar enganxada.
|
||||||
|
if (capture_requested_ && ensureCaptureResources()) {
|
||||||
|
compositeTo(capture_texture_);
|
||||||
|
if (render_pass_ != nullptr) {
|
||||||
|
SDL_EndGPURenderPass(render_pass_);
|
||||||
|
render_pass_ = nullptr;
|
||||||
|
}
|
||||||
|
downloadCapture(); // fa submit + fence + map; deixa cmd_buffer_ a nullptr
|
||||||
|
} else {
|
||||||
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
SDL_SubmitGPUCommandBuffer(cmd_buffer_);
|
||||||
cmd_buffer_ = nullptr;
|
cmd_buffer_ = nullptr;
|
||||||
|
}
|
||||||
|
capture_requested_ = false;
|
||||||
swapchain_texture_ = nullptr;
|
swapchain_texture_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto GpuFrameRenderer::ensureCaptureResources() -> bool {
|
||||||
|
SDL_GPUDevice* dev = device_.get();
|
||||||
|
if (dev == nullptr || swapchain_w_ == 0 || swapchain_h_ == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const int W = static_cast<int>(swapchain_w_);
|
||||||
|
const int H = static_cast<int>(swapchain_h_);
|
||||||
|
if (capture_texture_ != nullptr && capture_tex_w_ == W && capture_tex_h_ == H) {
|
||||||
|
return true; // ja són de la mida correcta
|
||||||
|
}
|
||||||
|
destroyCaptureResources();
|
||||||
|
|
||||||
|
// Mateix format que la swapchain: és el target del postfx_pipeline_.
|
||||||
|
SDL_GPUTextureCreateInfo tex_info{};
|
||||||
|
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||||
|
tex_info.format = device_.swapchainFormat();
|
||||||
|
tex_info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
|
||||||
|
tex_info.width = swapchain_w_;
|
||||||
|
tex_info.height = swapchain_h_;
|
||||||
|
tex_info.layer_count_or_depth = 1;
|
||||||
|
tex_info.num_levels = 1;
|
||||||
|
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||||
|
capture_texture_ = SDL_CreateGPUTexture(dev, &tex_info);
|
||||||
|
if (capture_texture_ == nullptr) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] CreateGPUTexture (captura): " << SDL_GetError() << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUTransferBufferCreateInfo tbo_info{};
|
||||||
|
tbo_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD;
|
||||||
|
tbo_info.size = static_cast<uint32_t>(W * H * 4);
|
||||||
|
capture_transfer_ = SDL_CreateGPUTransferBuffer(dev, &tbo_info);
|
||||||
|
if (capture_transfer_ == nullptr) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] CreateGPUTransferBuffer (captura): " << SDL_GetError() << '\n';
|
||||||
|
SDL_ReleaseGPUTexture(dev, capture_texture_);
|
||||||
|
capture_texture_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
capture_tex_w_ = W;
|
||||||
|
capture_tex_h_ = H;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::downloadCapture() {
|
||||||
|
SDL_GPUDevice* dev = device_.get();
|
||||||
|
|
||||||
|
// Copy pass: descarrega la textura de captura al transfer buffer.
|
||||||
|
SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd_buffer_);
|
||||||
|
SDL_GPUTextureRegion src{};
|
||||||
|
src.texture = capture_texture_;
|
||||||
|
src.w = swapchain_w_;
|
||||||
|
src.h = swapchain_h_;
|
||||||
|
src.d = 1;
|
||||||
|
SDL_GPUTextureTransferInfo dst{};
|
||||||
|
dst.transfer_buffer = capture_transfer_;
|
||||||
|
dst.offset = 0;
|
||||||
|
dst.pixels_per_row = swapchain_w_;
|
||||||
|
dst.rows_per_layer = swapchain_h_;
|
||||||
|
SDL_DownloadFromGPUTexture(copy_pass, &src, &dst);
|
||||||
|
SDL_EndGPUCopyPass(copy_pass);
|
||||||
|
|
||||||
|
// Submit amb fence i esperar: el readback ha d'estar complet abans de mapar.
|
||||||
|
SDL_GPUFence* fence = SDL_SubmitGPUCommandBufferAndAcquireFence(cmd_buffer_);
|
||||||
|
cmd_buffer_ = nullptr;
|
||||||
|
if (fence == nullptr) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] SubmitAndAcquireFence (captura): " << SDL_GetError() << '\n';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SDL_WaitForGPUFences(dev, true, &fence, 1);
|
||||||
|
SDL_ReleaseGPUFence(dev, fence);
|
||||||
|
|
||||||
|
const auto* mapped = static_cast<const uint8_t*>(SDL_MapGPUTransferBuffer(dev, capture_transfer_, false));
|
||||||
|
if (mapped == nullptr) {
|
||||||
|
std::cerr << "[GpuFrameRenderer] MapGPUTransferBuffer (captura): " << SDL_GetError() << '\n';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversió a ARGB8888 (0xAARRGGBB), que és el que espera Screenshot::save.
|
||||||
|
// El swapchain a Linux/Vulkan sol ser B8G8R8A8_UNORM (bytes B,G,R,A);
|
||||||
|
// altrament tractem com R8G8B8A8_UNORM (bytes R,G,B,A). Alpha forçat a 255
|
||||||
|
// perquè el composite ja escriu opac.
|
||||||
|
const int W = static_cast<int>(swapchain_w_);
|
||||||
|
const int H = static_cast<int>(swapchain_h_);
|
||||||
|
const auto COUNT = static_cast<std::size_t>(W) * static_cast<std::size_t>(H);
|
||||||
|
capture_pixels_.resize(COUNT);
|
||||||
|
const bool BGRA = device_.swapchainFormat() == SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||||
|
for (std::size_t i = 0; i < COUNT; ++i) {
|
||||||
|
const uint8_t* px = mapped + (i * 4);
|
||||||
|
const std::uint32_t R = BGRA ? px[2] : px[0];
|
||||||
|
const std::uint32_t G = px[1];
|
||||||
|
const std::uint32_t B = BGRA ? px[0] : px[2];
|
||||||
|
capture_pixels_[i] = (0xFFU << 24) | (R << 16) | (G << 8) | B;
|
||||||
|
}
|
||||||
|
SDL_UnmapGPUTransferBuffer(dev, capture_transfer_);
|
||||||
|
|
||||||
|
capture_w_ = W;
|
||||||
|
capture_h_ = H;
|
||||||
|
capture_ready_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GpuFrameRenderer::destroyCaptureResources() {
|
||||||
|
SDL_GPUDevice* dev = device_.get();
|
||||||
|
if (dev == nullptr) {
|
||||||
|
capture_texture_ = nullptr;
|
||||||
|
capture_transfer_ = nullptr;
|
||||||
|
capture_tex_w_ = 0;
|
||||||
|
capture_tex_h_ = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (capture_texture_ != nullptr) {
|
||||||
|
SDL_ReleaseGPUTexture(dev, capture_texture_);
|
||||||
|
capture_texture_ = nullptr;
|
||||||
|
}
|
||||||
|
if (capture_transfer_ != nullptr) {
|
||||||
|
SDL_ReleaseGPUTransferBuffer(dev, capture_transfer_);
|
||||||
|
capture_transfer_ = nullptr;
|
||||||
|
}
|
||||||
|
capture_tex_w_ = 0;
|
||||||
|
capture_tex_h_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Rendering::GPU
|
} // namespace Rendering::GPU
|
||||||
|
|||||||
@@ -133,6 +133,19 @@ namespace Rendering::GPU {
|
|||||||
void setPostFxEnabled(bool enabled) { postfx_enabled_ = enabled; }
|
void setPostFxEnabled(bool enabled) { postfx_enabled_ = enabled; }
|
||||||
[[nodiscard]] auto isPostFxEnabled() const -> bool { return postfx_enabled_; }
|
[[nodiscard]] auto isPostFxEnabled() const -> bool { return postfx_enabled_; }
|
||||||
|
|
||||||
|
// === Captura de pantalla (screenshot) ===
|
||||||
|
// Demana una captura del frame actual: el pròxim endFrame fa un segon
|
||||||
|
// composite sobre una textura pròpia (mida swapchain) i el descarrega a
|
||||||
|
// CPU en ARGB8888. Cost zero quan no hi ha cap petició pendent. Després
|
||||||
|
// del present, el caller comprova hasCapture(), llig captureData() i
|
||||||
|
// crida clearCapture().
|
||||||
|
void requestCapture() { capture_requested_ = true; }
|
||||||
|
[[nodiscard]] auto hasCapture() const -> bool { return capture_ready_; }
|
||||||
|
[[nodiscard]] auto captureData() const -> const std::uint32_t* { return capture_pixels_.data(); }
|
||||||
|
[[nodiscard]] auto captureWidth() const -> int { return capture_w_; }
|
||||||
|
[[nodiscard]] auto captureHeight() const -> int { return capture_h_; }
|
||||||
|
void clearCapture() { capture_ready_ = false; }
|
||||||
|
|
||||||
// Acceso a internals.
|
// Acceso a internals.
|
||||||
[[nodiscard]] auto device() -> GpuDevice& { return device_; }
|
[[nodiscard]] auto device() -> GpuDevice& { return device_; }
|
||||||
[[nodiscard]] auto isInsideFrame() const -> bool { return cmd_buffer_ != nullptr; }
|
[[nodiscard]] auto isInsideFrame() const -> bool { return cmd_buffer_ != nullptr; }
|
||||||
@@ -173,6 +186,26 @@ namespace Rendering::GPU {
|
|||||||
SDL_GPUTexture* bloom_texture_a_{nullptr};
|
SDL_GPUTexture* bloom_texture_a_{nullptr};
|
||||||
SDL_GPUTexture* bloom_texture_b_{nullptr};
|
SDL_GPUTexture* bloom_texture_b_{nullptr};
|
||||||
|
|
||||||
|
// === Captura de pantalla ===
|
||||||
|
// Dimensions reals de la swapchain (capturades a beginFrame). El target
|
||||||
|
// de captura es crea a aquesta mida perquè el PNG surti exactament com
|
||||||
|
// es veu en pantalla (amb letterbox), no a la resolució del offscreen.
|
||||||
|
uint32_t swapchain_w_{0};
|
||||||
|
uint32_t swapchain_h_{0};
|
||||||
|
// Textura on es fa el segon composite (mateix format que la swapchain) i
|
||||||
|
// transfer buffer per a descarregar-la a CPU. Es recreen si canvia la
|
||||||
|
// mida de la finestra (capture_tex_w_/h_ = mida amb què es van crear).
|
||||||
|
SDL_GPUTexture* capture_texture_{nullptr};
|
||||||
|
SDL_GPUTransferBuffer* capture_transfer_{nullptr};
|
||||||
|
int capture_tex_w_{0};
|
||||||
|
int capture_tex_h_{0};
|
||||||
|
// Píxels descarregats (ARGB8888, 0xAARRGGBB) i estat de la petició.
|
||||||
|
std::vector<std::uint32_t> capture_pixels_;
|
||||||
|
int capture_w_{0};
|
||||||
|
int capture_h_{0};
|
||||||
|
bool capture_requested_{false};
|
||||||
|
bool capture_ready_{false};
|
||||||
|
|
||||||
// Batch del frame en curso.
|
// Batch del frame en curso.
|
||||||
std::vector<LineVertex> vertices_;
|
std::vector<LineVertex> vertices_;
|
||||||
std::vector<uint16_t> indices_;
|
std::vector<uint16_t> indices_;
|
||||||
@@ -202,8 +235,14 @@ namespace Rendering::GPU {
|
|||||||
void flushBatch();
|
void flushBatch();
|
||||||
void bloomPass(); // pre-composite: H + V passes sobre les bloom textures
|
void bloomPass(); // pre-composite: H + V passes sobre les bloom textures
|
||||||
void compositePass();
|
void compositePass();
|
||||||
|
void compositeTo(SDL_GPUTexture* target_tex); // cos comú del pase de postpro
|
||||||
void applyFinalViewport();
|
void applyFinalViewport();
|
||||||
void applyCurrentScissor(); // re-aplica el top de clip_stack_ al render_pass_
|
void applyCurrentScissor(); // re-aplica el top de clip_stack_ al render_pass_
|
||||||
|
|
||||||
|
// Captura: (re)crea recursos a mida swapchain, descarrega a CPU i allibera.
|
||||||
|
[[nodiscard]] auto ensureCaptureResources() -> bool;
|
||||||
|
void downloadCapture(); // copy pass + fence + map → capture_pixels_
|
||||||
|
void destroyCaptureResources();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Rendering::GPU
|
} // namespace Rendering::GPU
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#include "core/rendering/screenshot.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ctime>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "external/stb_image_write.h"
|
||||||
|
|
||||||
|
namespace Screenshot {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Estat de mòdul: ruta base on s'escriu la subcarpeta
|
||||||
|
// `screenshots/`. Buida per defecte ⇒ relativa al CWD (fallback
|
||||||
|
// si el Director no ha pogut establir la ruta per plataforma).
|
||||||
|
std::string g_base_dir; // NOLINT(*-avoid-non-const-global-variables) -- estat de mòdul (singleton-like): s'estableix una vegada al startup via setBaseDir() i es llegeix N vegades; encapsulat dins del namespace anònim, no és accessible des de fora.
|
||||||
|
|
||||||
|
// Construeix la ruta del directori on van les captures:
|
||||||
|
// `<base>/screenshots/`. Si la base és buida, retorna
|
||||||
|
// "screenshots/" relativa al CWD.
|
||||||
|
auto screenshotsDir() -> std::string {
|
||||||
|
if (g_base_dir.empty()) { return "screenshots/"; }
|
||||||
|
const bool ENDS_WITH_SEP = (g_base_dir.back() == '/' || g_base_dir.back() == '\\');
|
||||||
|
return g_base_dir + (ENDS_WITH_SEP ? "" : "/") + "screenshots/";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converteix ARGB8888 → RGBA8888 in-place i escriu el PNG. stb_image_write
|
||||||
|
// espera RGBA en ordre byte (little-endian: R al byte baix, A al byte alt).
|
||||||
|
auto writePng(std::uint32_t* buffer, int width, int height, const std::string& filepath) -> bool {
|
||||||
|
const int TOTAL = width * height;
|
||||||
|
for (int i = 0; i < TOTAL; ++i) {
|
||||||
|
const std::uint32_t P = buffer[i];
|
||||||
|
const std::uint32_t A = (P >> 24) & 0xFF;
|
||||||
|
const std::uint32_t R = (P >> 16) & 0xFF;
|
||||||
|
const std::uint32_t G = (P >> 8) & 0xFF;
|
||||||
|
const std::uint32_t B = P & 0xFF;
|
||||||
|
buffer[i] = R | (G << 8) | (B << 16) | (A << 24);
|
||||||
|
}
|
||||||
|
return stbi_write_png(filepath.c_str(), width, height, 4, buffer, width * 4) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void setBaseDir(std::string dir) {
|
||||||
|
g_base_dir = std::move(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto save(const std::uint32_t* buffer, int width, int height) -> Result {
|
||||||
|
const std::string FOLDER = screenshotsDir();
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::create_directories(FOLDER, ec);
|
||||||
|
|
||||||
|
const auto NOW = std::chrono::system_clock::now();
|
||||||
|
const std::time_t TIME = std::chrono::system_clock::to_time_t(NOW);
|
||||||
|
const std::tm* tm = std::localtime(&TIME);
|
||||||
|
char timestamp[20];
|
||||||
|
std::strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", tm);
|
||||||
|
|
||||||
|
const std::string FILENAME = std::string("scr_") + timestamp + ".png";
|
||||||
|
const std::string FILEPATH = FOLDER + FILENAME;
|
||||||
|
|
||||||
|
// Còpia local per no tocar el canvas original durant la conversió.
|
||||||
|
std::vector<std::uint32_t> copy(buffer, buffer + (static_cast<size_t>(width) * height));
|
||||||
|
if (!writePng(copy.data(), width, height, FILEPATH)) { return {}; }
|
||||||
|
return {.filename = FILENAME, .folder = FOLDER};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Screenshot
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Volca un buffer ARGB8888 a un fitxer PNG dins de la subcarpeta
|
||||||
|
// `screenshots/` del directori configurat amb `setBaseDir`. El nom es
|
||||||
|
// genera amb un timestamp i la funció retorna el basename i la ruta
|
||||||
|
// del directori on s'ha escrit (o un Result amb `filename` buit si
|
||||||
|
// l'escriptura ha fallat).
|
||||||
|
namespace Screenshot {
|
||||||
|
|
||||||
|
// Configura el directori base on s'escriuran les captures (s'hi
|
||||||
|
// crea una subcarpeta `screenshots/`). El Director el crida una
|
||||||
|
// vegada al boot amb `system_folder_` perquè les captures vagin
|
||||||
|
// al mateix lloc per plataforma que la resta de fitxers generats
|
||||||
|
// (config.yaml, gamepad_configs.yaml, shaders/, etc.). Si no es
|
||||||
|
// crida, el fallback és el CWD — comportament antic.
|
||||||
|
void setBaseDir(std::string dir);
|
||||||
|
|
||||||
|
// Resultat d'una captura: el `filename` és el basename del PNG
|
||||||
|
// (per a notificacions/títol) i el `folder` és la ruta absoluta
|
||||||
|
// on s'ha guardat (per a poder dir "guardada a {folder}"). Si
|
||||||
|
// l'escriptura falla, ambdós camps tornen buits.
|
||||||
|
struct Result {
|
||||||
|
std::string filename;
|
||||||
|
std::string folder;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto save(const std::uint32_t* buffer, int width, int height) -> Result;
|
||||||
|
|
||||||
|
} // namespace Screenshot
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "core/input/mouse.hpp"
|
#include "core/input/mouse.hpp"
|
||||||
#include "core/locale/locale.hpp"
|
#include "core/locale/locale.hpp"
|
||||||
#include "core/rendering/coordinate_transform.hpp"
|
#include "core/rendering/coordinate_transform.hpp"
|
||||||
|
#include "core/rendering/screenshot.hpp"
|
||||||
#include "core/system/notifier.hpp"
|
#include "core/system/notifier.hpp"
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
|
|
||||||
@@ -360,8 +361,36 @@ auto SDLManager::clear(uint8_t r, uint8_t g, uint8_t b) -> bool {
|
|||||||
return gpu_renderer_.beginFrame(0.0F, 0.0F, 0.0F);
|
return gpu_renderer_.beginFrame(0.0F, 0.0F, 0.0F);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SDLManager::requestScreenshot() {
|
||||||
|
// La captura es fa dins del pròxim endFrame (segon composite + readback);
|
||||||
|
// el resultat es recull ací mateix, a present(), un cop el frame ja s'ha
|
||||||
|
// compost. Així el PNG mostra exactament el que es veu en pantalla.
|
||||||
|
gpu_renderer_.requestCapture();
|
||||||
|
}
|
||||||
|
|
||||||
void SDLManager::present() {
|
void SDLManager::present() {
|
||||||
gpu_renderer_.endFrame();
|
gpu_renderer_.endFrame();
|
||||||
|
|
||||||
|
// Si el frame que s'acaba de presentar portava una captura demanada,
|
||||||
|
// l'escrivim a PNG i notifiquem la ruta. La notificació arriba DESPRÉS de
|
||||||
|
// la captura, així que el toast "guardada" no apareix dins de la imatge.
|
||||||
|
if (gpu_renderer_.hasCapture()) {
|
||||||
|
const auto RESULT = Screenshot::save(
|
||||||
|
gpu_renderer_.captureData(),
|
||||||
|
gpu_renderer_.captureWidth(),
|
||||||
|
gpu_renderer_.captureHeight());
|
||||||
|
gpu_renderer_.clearCapture();
|
||||||
|
if (!RESULT.filename.empty()) {
|
||||||
|
if (auto* notifier = System::Notifier::get(); notifier != nullptr) {
|
||||||
|
std::string msg = localeSubstitute(
|
||||||
|
Locale::get().text("notification.screenshot"),
|
||||||
|
"{file}",
|
||||||
|
RESULT.filename);
|
||||||
|
msg = localeSubstitute(msg, "{folder}", RESULT.folder);
|
||||||
|
notifier->notifyInfo(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLManager::toggleVSync() {
|
void SDLManager::toggleVSync() {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class SDLManager {
|
|||||||
void toggleVSync(); // F4
|
void toggleVSync(); // F4
|
||||||
void toggleAntialias(); // F5
|
void toggleAntialias(); // F5
|
||||||
void togglePostFx(); // F6
|
void togglePostFx(); // F6
|
||||||
|
void requestScreenshot(); // F9: demana una captura del pròxim frame
|
||||||
// Canvia la resolució del render target offscreen (recrea la textura).
|
// Canvia la resolució del render target offscreen (recrea la textura).
|
||||||
// Cal cridar-lo fora d'un frame (event phase, no draw phase). Si el
|
// Cal cridar-lo fora d'un frame (event phase, no draw phase). Si el
|
||||||
// valor no es un preset valid o ja es l'actual, es no-op.
|
// valor no es un preset valid o ja es l'actual, es no-op.
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#include "core/input/input.hpp"
|
#include "core/input/input.hpp"
|
||||||
#include "core/input/mouse.hpp"
|
#include "core/input/mouse.hpp"
|
||||||
#include "core/locale/locale.hpp"
|
#include "core/locale/locale.hpp"
|
||||||
|
#include "core/rendering/screenshot.hpp"
|
||||||
#include "core/rendering/sdl_manager.hpp"
|
#include "core/rendering/sdl_manager.hpp"
|
||||||
#include "core/resources/resource_helper.hpp"
|
#include "core/resources/resource_helper.hpp"
|
||||||
#include "core/resources/resource_loader.hpp"
|
#include "core/resources/resource_loader.hpp"
|
||||||
@@ -99,6 +100,10 @@ Director::Director(int argc, char* argv[])
|
|||||||
// Establir ruta del file de configuración
|
// Establir ruta del file de configuración
|
||||||
ConfigYaml::setConfigFile(system_folder_ + "/config.yaml");
|
ConfigYaml::setConfigFile(system_folder_ + "/config.yaml");
|
||||||
|
|
||||||
|
// Les captures de pantalla van sota la mateixa carpeta per plataforma
|
||||||
|
// (subcarpeta screenshots/). Sense açò, Screenshot::save cauria al CWD.
|
||||||
|
Screenshot::setBaseDir(system_folder_);
|
||||||
|
|
||||||
// Carregar o crear configuración
|
// Carregar o crear configuración
|
||||||
ConfigYaml::loadFromFile();
|
ConfigYaml::loadFromFile();
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,11 @@ namespace GlobalEvents {
|
|||||||
sdl.togglePostFx();
|
sdl.togglePostFx();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case SDL_SCANCODE_F9:
|
||||||
|
// Captura de pantalla (PNG) amb shaders, a mida de finestra.
|
||||||
|
sdl.requestScreenshot();
|
||||||
|
return true;
|
||||||
|
|
||||||
case SDL_SCANCODE_F7: {
|
case SDL_SCANCODE_F7: {
|
||||||
// Toggle d'idioma en runtime entre català i anglès. Els
|
// Toggle d'idioma en runtime entre català i anglès. Els
|
||||||
// strings ja capturats (toast actiu, banner stage start)
|
// strings ja capturats (toast actiu, banner stage start)
|
||||||
|
|||||||
Vendored
+1724
File diff suppressed because it is too large
Load Diff
+13
@@ -0,0 +1,13 @@
|
|||||||
|
// Unitat de compilació aïllada per a la implementació de stb_image_write.
|
||||||
|
// Viu dins de source/external/ perquè el `.clang-tidy` d'aquesta carpeta
|
||||||
|
// desactiva tots els checks (com fa per stb_vorbis.c) i el pre-commit hook
|
||||||
|
// ja filtra aquesta ruta de clang-format / clang-tidy. Així els fals
|
||||||
|
// positius de clang-analyzer-* dins de codi de tercers no afecten el
|
||||||
|
// nostre codi, que continua tenint tots els checks actius.
|
||||||
|
//
|
||||||
|
// La resta del codi inclou només el header (sense la macro d'implementació),
|
||||||
|
// que queda només amb declaracions — clang-analyzer no pot trobar cap bug
|
||||||
|
// dins d'una declaració, així que l'inclusió és innòcua.
|
||||||
|
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include "external/stb_image_write.h"
|
||||||
@@ -408,8 +408,15 @@ auto GameScene::stepDemo(float delta_time, bool input_blocked) -> bool {
|
|||||||
demo_ctrls_[i] = {}; // nau inactiva/morta: sense control
|
demo_ctrls_[i] = {}; // nau inactiva/morta: sense control
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Company per a evitar foc amic en demo de 2 naus: només es passa si l'altre
|
||||||
|
// jugador està actiu al match (un slot no usat tindria isActive()==true).
|
||||||
|
const uint8_t OTHER = 1U - i;
|
||||||
|
const bool OTHER_ACTIVE = (OTHER == 0) ? match_config_.player1_active
|
||||||
|
: match_config_.player2_active;
|
||||||
|
const Ship* teammate = OTHER_ACTIVE ? &ships_[OTHER] : nullptr;
|
||||||
demo_ctrls_[i] = demo_pilots_[i].compute(
|
demo_ctrls_[i] = demo_pilots_[i].compute(
|
||||||
ships_[i],
|
ships_[i],
|
||||||
|
teammate,
|
||||||
enemies_,
|
enemies_,
|
||||||
bullets_,
|
bullets_,
|
||||||
Defaults::Zones::PLAYAREA,
|
Defaults::Zones::PLAYAREA,
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class TitleScene final : public Scene {
|
|||||||
static constexpr float ORBIT_PHASE_OFFSET = 1.57F;
|
static constexpr float ORBIT_PHASE_OFFSET = 1.57F;
|
||||||
|
|
||||||
static constexpr float SHADOW_DELAY = 0.5F;
|
static constexpr float SHADOW_DELAY = 0.5F;
|
||||||
static constexpr float SHADOW_BRIGHTNESS = 0.4F;
|
static constexpr float SHADOW_BRIGHTNESS = 0.55F;
|
||||||
static constexpr float SHADOW_OFFSET_X = 2.0F;
|
static constexpr float SHADOW_OFFSET_X = 2.0F;
|
||||||
static constexpr float SHADOW_OFFSET_Y = 2.0F;
|
static constexpr float SHADOW_OFFSET_Y = 2.0F;
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ namespace Systems::Demo {
|
|||||||
constexpr float DODGE_SCAN_RADIUS = 190.0F; // px: distancia a la que reacciona
|
constexpr float DODGE_SCAN_RADIUS = 190.0F; // px: distancia a la que reacciona
|
||||||
constexpr float DODGE_HEADING_MIN = 0.25F; // dot mínimo: la bala viene hacia la nave
|
constexpr float DODGE_HEADING_MIN = 0.25F; // dot mínimo: la bala viene hacia la nave
|
||||||
|
|
||||||
|
// Foc amic: retindre el tret si el company està en la línia de tir.
|
||||||
|
constexpr float FRIENDLY_BLOCK_RANGE = 1200.0F; // px endavant que es vigilen
|
||||||
|
constexpr float FRIENDLY_BLOCK_MARGIN = 22.0F; // px: marge sobre el radi del company
|
||||||
|
|
||||||
// [-1, 1] aleatorio (estética: jitter de apuntado; no afecta a la simulación).
|
// [-1, 1] aleatorio (estética: jitter de apuntado; no afecta a la simulación).
|
||||||
auto randSigned() -> float {
|
auto randSigned() -> float {
|
||||||
return (static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX) * 2.0F) - 1.0F;
|
return (static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX) * 2.0F) - 1.0F;
|
||||||
@@ -136,9 +140,26 @@ namespace Systems::Demo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ¿El company està en la línia de tir? (davant del morro, dins de l'abast i
|
||||||
|
// a prou poca distància perpendicular de la trajectòria recta de la bala).
|
||||||
|
// La bala ix en la direcció del morro: (cos, sin)(angle - PI/2).
|
||||||
|
auto teammateInLineOfFire(const Ship& ship, const Ship& mate) -> bool {
|
||||||
|
const float NOSE = ship.getAngle() - (PI / 2.0F);
|
||||||
|
const Vec2 FORWARD{.x = std::cos(NOSE), .y = std::sin(NOSE)};
|
||||||
|
const Vec2 TO_MATE = mate.getCenter() - ship.getCenter();
|
||||||
|
const float ALONG = TO_MATE.dot(FORWARD); // distància al llarg del tret
|
||||||
|
if (ALONG <= 0.0F || ALONG > FRIENDLY_BLOCK_RANGE) {
|
||||||
|
return false; // darrere de la nau o massa lluny
|
||||||
|
}
|
||||||
|
const float PERP2 = TO_MATE.lengthSquared() - (ALONG * ALONG);
|
||||||
|
const float CLEAR = mate.getCollisionRadius() + FRIENDLY_BLOCK_MARGIN;
|
||||||
|
return PERP2 < CLEAR * CLEAR;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
auto DemoPilot::compute(const Ship& ship,
|
auto DemoPilot::compute(const Ship& ship,
|
||||||
|
const Ship* teammate,
|
||||||
const std::array<Enemy, Constants::MAX_ORNIS>& enemies,
|
const std::array<Enemy, Constants::MAX_ORNIS>& enemies,
|
||||||
const std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS_TOTAL)>& bullets,
|
const std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS_TOTAL)>& bullets,
|
||||||
const SDL_FRect& play_area,
|
const SDL_FRect& play_area,
|
||||||
@@ -218,12 +239,20 @@ namespace Systems::Demo {
|
|||||||
std::fabs(ERROR) < FIRE_TOLERANCE) {
|
std::fabs(ERROR) < FIRE_TOLERANCE) {
|
||||||
ctrl.thrust = true;
|
ctrl.thrust = true;
|
||||||
}
|
}
|
||||||
// Disparar cuando está bien encarada y el cooldown lo permite.
|
// Disparar cuando está bien encarada y el cooldown lo permite, però NO
|
||||||
|
// si el company està en la línia de tir (i seria víctima vàlida de foc
|
||||||
|
// amic): es retén el tret sense gastar cooldown, així dispara tan prompte
|
||||||
|
// com l'altra nau isca de la línia.
|
||||||
if (std::fabs(ERROR) < FIRE_TOLERANCE && fire_cooldown_ <= 0.0F) {
|
if (std::fabs(ERROR) < FIRE_TOLERANCE && fire_cooldown_ <= 0.0F) {
|
||||||
|
const bool FRIENDLY_BLOCK = (teammate != nullptr) &&
|
||||||
|
teammate->isActive() && !teammate->isInvulnerable() &&
|
||||||
|
teammateInLineOfFire(ship, *teammate);
|
||||||
|
if (!FRIENDLY_BLOCK) {
|
||||||
ctrl.shoot = true;
|
ctrl.shoot = true;
|
||||||
fire_cooldown_ = FIRE_COOLDOWN;
|
fire_cooldown_ = FIRE_COOLDOWN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ctrl;
|
return ctrl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ namespace Systems::Demo {
|
|||||||
class DemoPilot {
|
class DemoPilot {
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] auto compute(const Ship& ship,
|
[[nodiscard]] auto compute(const Ship& ship,
|
||||||
|
const Ship* teammate, // altra nau (nullptr si no n'hi ha): evita foc amic
|
||||||
const std::array<Enemy, Constants::MAX_ORNIS>& enemies,
|
const std::array<Enemy, Constants::MAX_ORNIS>& enemies,
|
||||||
const std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS_TOTAL)>& bullets,
|
const std::array<Bullet, static_cast<std::size_t>(Defaults::Entities::MAX_BULLETS_TOTAL)>& bullets,
|
||||||
const SDL_FRect& play_area,
|
const SDL_FRect& play_area,
|
||||||
|
|||||||
Reference in New Issue
Block a user