Codename Hamster - For olcCodeJam 2024
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hamster/src/HamsterGame.cpp

333 lines
13 KiB

#include "HamsterGame.h"
#include "Hamster.h"
#include <stdexcept>
#include <ranges>
#include "util.h"
geom2d::rect<float>HamsterGame::SCREEN_FRAME{{96,0},{320,288}};
std::unordered_map<std::string,Animate2D::Animation<HamsterGame::AnimationState>>HamsterGame::ANIMATIONS;
std::unordered_map<std::string,Renderable>HamsterGame::GFX;
const std::string HamsterGame::ASSETS_DIR{"assets/"};
HamsterGame*HamsterGame::self{nullptr};
std::unordered_map<uint32_t,Animate2D::FrameSequence>HamsterGame::ANIMATED_TILE_IDS;
HamsterGame::HamsterGame(){
sAppName = "Project Hamster";
HamsterGame::self=this;
}
bool HamsterGame::OnUserCreate(){
olc::GFX3D::ConfigureDisplay();
camera=Camera2D{SCREEN_FRAME.size};
camera.SetMode(Camera2D::Mode::LazyFollow);
tv.Initialise(SCREEN_FRAME.size,{1,1});
LoadGraphics();
LoadAnimations();
currentTileset=TSXParser{ASSETS_DIR+std::string("Terrain.tsx")};
LoadLevel("TestLevel.tmx"); //THIS IS TEMPORARY.
border.ChangeBorder(Border::DEFAULT);
renderer.SetProjection(90.0f, (float)SCREEN_FRAME.size.y/(float)SCREEN_FRAME.size.x, 0.1f, 1000.0f, 0.0f, 0.0f, SCREEN_FRAME.size.x, SCREEN_FRAME.size.y);
return true;
}
void HamsterGame::_LoadImage(const std::string_view img){
GFX.insert({ASSETS_DIR+std::string(img),Renderable{}});
rcode result{GFX[ASSETS_DIR+std::string(img)].Load(ASSETS_DIR+std::string(img))};
if(result!=OK)throw std::runtime_error{std::format("Failed to Load Image {}. OLC Rcode: {}",img,int(result))};
}
void HamsterGame::LoadGraphics(){
_LoadImage("border.png");
_LoadImage("gametiles.png");
_LoadImage("shadow.png");
_LoadImage("drownmeter.png");
_LoadImage("burnmeter.png");
_LoadImage("hamster_jet.png");
UpdateMatrixTexture();
}
void HamsterGame::LoadAnimations(){
auto LoadImageIfRequired=[this](const std::string_view img){if(!GFX.count(ASSETS_DIR+std::string(img)))_LoadImage(img);};
auto LoadStillAnimation=[this,&LoadImageIfRequired](const AnimationState state,const std::string_view img){
Animate2D::FrameSequence stillAnimation{0.f,Animate2D::Style::OneShot};
LoadImageIfRequired(img);
stillAnimation.AddFrame(Animate2D::Frame{&GetGFX(img),{{},GetGFX(img).Sprite()->Size()}});
ANIMATIONS[ASSETS_DIR+std::string(img)].AddState(state,stillAnimation);
};
auto LoadAnimation=[this,&LoadImageIfRequired](const AnimationState state,const std::string_view img,const std::vector<vf2d>frames,const float frameDuration=0.1f,const Animate2D::Style style=Animate2D::Style::Repeat,vf2d frameSize={32,32}){
Animate2D::FrameSequence newAnimation{frameDuration,style};
LoadImageIfRequired(img);
for(const vf2d&framePos:frames){
newAnimation.AddFrame(Animate2D::Frame{&GetGFX(img),{framePos,frameSize}});
}
ANIMATIONS[ASSETS_DIR+std::string(img)].AddState(state,newAnimation);
};
LoadAnimation(DEFAULT,"hamster.png",{{0,32},{32,32}},0.3f);
LoadAnimation(WHEEL_TOP,"hamster.png",{{0,96},{32,96}},0.1f);
LoadAnimation(WHEEL_BOTTOM,"hamster.png",{{64,96},{96,96}},0.1f);
Animate2D::FrameSequence&waterAnimFrames{(*ANIMATED_TILE_IDS.insert({1384,Animate2D::FrameSequence{0.2f}}).first).second};
for(vf2d&sourcePos:std::vector<vf2d>{{192+16*0,784},{192+16*1,784},{192+16*2,784},{192+16*3,784},{192+16*4,784},{192+16*5,784},{192+16*6,784},{192+16*7,784}}){
waterAnimFrames.AddFrame(Animate2D::Frame{&GetGFX("gametiles.png"),{sourcePos,{16,16}}});
}
Animate2D::FrameSequence&lavaAnimFrames{(*ANIMATED_TILE_IDS.insert({1412,Animate2D::FrameSequence{0.2f}}).first).second};
for(vf2d&sourcePos:std::vector<vf2d>{{192+16*0,800},{192+16*1,800},{192+16*2,800},{192+16*3,800},{192+16*4,800},{192+16*5,800},{192+16*6,800},{192+16*7,800},{192+16*8,800}}){
lavaAnimFrames.AddFrame(Animate2D::Frame{&GetGFX("gametiles.png"),{sourcePos,{16,16}}});
}
LoadAnimation(JET_LIGHTS,"hamster_jet.png",{{0,48},{48,48}},0.3f);
animatedWaterTile.Create(16,16,false,false);
UpdateWaterTexture();
}
void HamsterGame::LoadLevel(const std::string_view mapName){
const vf2d levelSpawnLoc{50,50}; //TEMPORARY
currentMap=TMXParser{ASSETS_DIR+std::string(mapName)};
Hamster::LoadHamsters(levelSpawnLoc);
camera.SetTarget(Hamster::GetPlayer().GetPos());
mapImage.Create(currentMap.value().GetData().GetMapData().width*16,currentMap.value().GetData().GetMapData().height*16);
SetDrawTarget(mapImage.Sprite());
Clear(BLANK);
SetPixelMode(Pixel::MASK);
#pragma region Detect powerup tiles
std::vector<Powerup>mapPowerups;
for(const LayerTag&layer:currentMap.value().GetData().GetLayers()){
for(size_t y:std::ranges::iota_view(0U,layer.tiles.size())){
for(size_t x:std::ranges::iota_view(0U,layer.tiles[y].size())){
const int tileID{layer.tiles[y][x]-1};
if(Powerup::TileIDIsUpperLeftPowerupTile(tileID))mapPowerups.emplace_back(vf2d{float(x),float(y)}*16+vf2d{16,16},Powerup::TileIDPowerupType(tileID));
const int numTilesWide{GetGFX("gametiles.png").Sprite()->width/16};
const int numTilesTall{GetGFX("gametiles.png").Sprite()->height/16};
int imgTileX{tileID%numTilesWide};
int imgTileY{tileID/numTilesWide};
if(tileID==-1||Powerup::TileIDIsPowerupTile(tileID))continue;
DrawPartialSprite(vf2d{float(x),float(y)}*16,GetGFX("gametiles.png").Sprite(),vf2d{float(imgTileX),float(imgTileY)}*16.f,vf2d{16.f,16.f});
}
}
}
Powerup::Initialize(mapPowerups);
#pragma endregion
mapImage.Decal()->Update();
SetPixelMode(Pixel::NORMAL);
SetDrawTarget(nullptr);
}
void HamsterGame::UpdateGame(const float fElapsedTime){
UpdateMatrixTexture();
UpdateWaterTexture();
camera.SetViewSize(tv.GetWorldVisibleArea());
camera.Update(fElapsedTime);
tv.SetWorldOffset(tv.ScaleToWorld(-SCREEN_FRAME.pos)+camera.GetViewPosition());
Hamster::UpdateHamsters(fElapsedTime);
Powerup::UpdatePowerups(fElapsedTime);
border.Update(fElapsedTime);
}
void HamsterGame::DrawGame(){
tv.DrawPartialDecal({-3200,-3200},currentMap.value().GetData().GetMapData().MapSize*16+vf2d{3200,3200},animatedWaterTile.Decal(),{0,0},currentMap.value().GetData().GetMapData().MapSize*16+vf2d{3200,3200});
DrawLevelTiles();
Powerup::DrawPowerups(tv);
Hamster::DrawHamsters(tv);
border.Draw();
#pragma region Powerup Display
for(int y:std::ranges::iota_view(0,4)){
for(int x:std::ranges::iota_view(0,2)){
const int powerupInd{y*2+x};
const float drawX{x*32.f+12.f};
const float drawY{y*32.f+12.f+96.f};
const Powerup::PowerupType powerupType{Powerup::PowerupType(powerupInd)};
const geom2d::rect<float>powerupSubimageRect{Powerup::GetPowerupSubimageRect(powerupType)};
if(Hamster::GetPlayer().HasPowerup(powerupType)){
SetDecalMode(DecalMode::ADDITIVE);
DrawPartialRotatedDecal(vf2d{drawX,drawY}+16,GetGFX("gametiles.png").Decal(),0.f,{16.f,16.f},powerupSubimageRect.pos,powerupSubimageRect.size,{1.1f,1.1f});
SetDecalMode(DecalMode::NORMAL);
DrawPartialDecal({drawX,drawY},GetGFX("gametiles.png").Decal(),powerupSubimageRect.pos,powerupSubimageRect.size);
}else{
DrawPartialDecal({drawX,drawY},GetGFX("gametiles.png").Decal(),powerupSubimageRect.pos,powerupSubimageRect.size,{1.f,1.f},VERY_DARK_GREY);
}
}
}
#pragma endregion
#pragma region Drown/Burn Bar.
if(Hamster::GetPlayer().IsDrowning()){
DrawDecal({12.f,240.f},GetGFX("drownmeter.png").Decal());
GradientFillRectDecal(vf2d{12.f,240.f}+vf2d{12.f,5.f},vf2d{Hamster::GetPlayer().GetDrownRatio()*57.f,4.f},{145,199,255},{226,228,255},{226,228,255},{145,199,255});
}
else if(Hamster::GetPlayer().IsBurning()){
DrawDecal({12.f,240.f},GetGFX("burnmeter.png").Decal());
GradientFillRectDecal(vf2d{12.f,240.f}+vf2d{12.f,5.f},vf2d{Hamster::GetPlayer().GetBurnRatio()*57.f,4.f},{250,177,163},{226,228,255},{226,228,255},{250,177,163});
}
#pragma endregion
}
const Terrain::TerrainType HamsterGame::GetTerrainTypeAtPos(const vf2d pos)const{
Terrain::TerrainType tileType{Terrain::VOID};
if(pos.x<=0.f||pos.y<=0.f||pos.x>=currentMap.value().GetData().GetMapData().width*16||pos.y>=currentMap.value().GetData().GetMapData().height*16)return tileType;
for(const LayerTag&layer:currentMap.value().GetData().GetLayers()){
int tileX{int(floor(pos.x)/16)};
int tileY{int(floor(pos.y)/16)};
int tileID{layer.tiles[tileY][tileX]-1};
if(tileID==-1)continue;
if(currentTileset.value().GetData().GetTerrainData().count(tileID))tileType=currentTileset.value().GetData().GetTerrainData().at(tileID).second;
}
return tileType;
}
const bool HamsterGame::IsTerrainSolid(const vf2d pos)const{
if(pos.x<=0.f||pos.y<=0.f||pos.x>=currentMap.value().GetData().GetMapData().width*16||pos.y>=currentMap.value().GetData().GetMapData().height*16)return true;
bool tileIsBlank{true};
for(const LayerTag&layer:currentMap.value().GetData().GetLayers()){
int tileX{int(floor(pos.x)/16)};
int tileY{int(floor(pos.y)/16)};
int tileID{layer.tiles[tileY][tileX]-1};
if(tileID==-1)continue;
tileIsBlank=false;
if(currentTileset.value().GetData().GetTerrainData().count(tileID)&&currentTileset.value().GetData().GetTerrainData().at(tileID).first==Terrain::SolidType::SOLID)return true;
}
return tileIsBlank;
}
void HamsterGame::DrawLevelTiles(){
float extendedBounds{SCREEN_FRAME.pos.x};
extendedBounds*=1/tv.GetWorldScale().x;
tv.DrawDecal({},mapImage.Decal());
for(const LayerTag&layer:currentMap.value().GetData().GetLayers()){
for(float y=tv.GetWorldTL().y-17;y<=tv.GetWorldBR().y+16;y+=16){
for(float x=tv.GetWorldTL().x-17+extendedBounds;x<=tv.GetWorldBR().x+16+extendedBounds;x+=16){
if(x<=0.f||y<=0.f||x>=currentMap.value().GetData().GetMapData().width*16||y>=currentMap.value().GetData().GetMapData().height*16)continue;
int tileX{int(floor(x)/16)};
int tileY{int(floor(y)/16)};
int tileID{layer.tiles[tileY][tileX]-1};
if(ANIMATED_TILE_IDS.count(tileID)){
Animate2D::FrameSequence&animatedTile{ANIMATED_TILE_IDS[tileID]};
const Animate2D::Frame&currentFrame{animatedTile.GetFrame(runTime)};
tv.DrawPartialDecal(vf2d{float(tileX),float(tileY)}*16,currentFrame.GetSourceImage()->Decal(),currentFrame.GetSourceRect().pos,currentFrame.GetSourceRect().size);
}else continue;
}
}
}
}
bool HamsterGame::OnUserUpdate(float fElapsedTime){
runTime+=fElapsedTime;
UpdateGame(fElapsedTime);
DrawGame();
return true;
}
const Renderable&HamsterGame::GetGFX(const std::string_view img){
if(!GFX.count(ASSETS_DIR+std::string(img)))throw std::runtime_error{std::format("Image {} does not exist!",img)};
return GFX[ASSETS_DIR+std::string(img)];
}
const Animate2D::Animation<HamsterGame::AnimationState>&HamsterGame::GetAnimations(const std::string_view img){
if(!ANIMATIONS.count(ASSETS_DIR+std::string(img)))throw std::runtime_error{std::format("Animations for {} does not exist!",img)};
return ANIMATIONS[ASSETS_DIR+std::string(img)];
}
bool HamsterGame::OnUserDestroy(){
ANIMATIONS.clear();
GFX.clear();
return true;
}
HamsterGame&HamsterGame::Game(){
return *self;
}
const double HamsterGame::GetRuntime()const{
return runTime;
}
void HamsterGame::UpdateMatrixTexture(){
const auto result{GFX.insert({ASSETS_DIR+"MATRIX_TEXTURE",Renderable{}})};
Renderable&texture{(*result.first).second};
if(result.second){
texture.Create(64,64,false,false);
}
const std::array<char,10>matrixLetters{'0','1','2','3','4','5','6','7','8','9'};
if(matrixTimer==0){
activeLetters.emplace_back(vf2d{float(rand()%64),float(64)},util::random(-40)-20,matrixLetters[rand()%matrixLetters.size()]);
matrixTimer=util::random(0.125);
}
if(updatePixelsTimer==0){
SetDrawTarget(texture.Sprite());
Sprite*img=texture.Sprite();
for(int y=63;y>=0;y--){
for(int x=63;x>=0;x--){
Pixel col=img->GetPixel(x,y);
if(col.r>0){
if(x>0){
Pixel leftCol=img->GetPixel(x-1,y);
if(leftCol.r<col.r){
leftCol=PixelLerp(col,leftCol,0.125);
}
Draw(x-1,y,leftCol);
}
if(x<img->width-1){
Pixel rightCol=img->GetPixel(x+1,y);
if(rightCol.r<col.r){
rightCol=PixelLerp(col,rightCol,0.125);
}
Draw(x+1,y,rightCol);
}
col/=8;
Draw(x,y,col);
}
}
}
for(int y=0;y<64;y++){
Draw({0,y},img->GetPixel(1,y));
}
SetDrawTarget(nullptr);
updatePixelsTimer=0.1;
}
if(activeLetters.size()>0){
SetDrawTarget(texture.Sprite());
for(Letter&letter:activeLetters){
letter.pos.y+=letter.spd*GetElapsedTime();
DrawString(letter.pos,std::string(1,letter.c));
}
SetDrawTarget(nullptr);
texture.Decal()->Update();
}
matrixTimer=std::max(0.f,matrixTimer-GetElapsedTime());
updatePixelsTimer=std::max(0.f,updatePixelsTimer-GetElapsedTime());
std::erase_if(activeLetters,[](Letter&letter){return letter.pos.y<-32;});
}
void HamsterGame::UpdateWaterTexture(){
const Animate2D::FrameSequence&waterAnimSequence{ANIMATED_TILE_IDS[1384]};
const Animate2D::Frame&frame{waterAnimSequence.GetFrame(GetRuntime())};
SetDrawTarget(animatedWaterTile.Sprite());
DrawPartialSprite({},frame.GetSourceImage()->Sprite(),frame.GetSourceRect().pos,frame.GetSourceRect().size);
SetDrawTarget(nullptr);
animatedWaterTile.Decal()->Update();
}
void HamsterGame::Apply3DTransform(std::vector<DecalInstance>&decals){
}
int main()
{
HamsterGame game;
if(game.Construct(512, 288, 3, 3))
game.Start();
return 0;
}