#define OLC_PGE_APPLICATION #define OLC_SOUNDWAVE #define OLC_PGEX_TRANSFORMEDVIEW #define AUDIO_LISTENER_IMPLEMENTATION #define AUDIO_SOURCE_IMPLEMENTATION #include "olcUTIL_Geometry2D.h" #include "TileManager.h" #include "util.h" #include "VirusAttack.h" VirusAttack::VirusAttack() { // Name your application sAppName = "olcCodeJam 2023 Entry"; } void VirusAttack::InitializeImages(){ auto LoadImage=[&](Image img,std::string filepath,bool filter=false,bool clamp=true){ IMAGES[img]=std::make_unique(); IMAGES[img]->Load(filepath,nullptr,filter,clamp); }; LoadImage(TILE,"assets/tile.png",false,false); LoadImage(MINIMAP_HUD,"assets/minimap_hud.png"); LoadImage(OUTLINE,"assets/outline.png"); LoadImage(VIRUS_IMG1,"assets/unit.png"); LoadImage(SELECTION_CIRCLE,"assets/selection_circle.png"); LoadImage(MEMORY_COLLECTION_POINT,"assets/memory_collection_point.png"); LoadImage(LEFT_SHIFTER,"assets/left_shifter.png"); LoadImage(RIGHT_SHIFTER,"assets/right_shifter.png"); LoadImage(BIT_RESTORER,"assets/bit_restorer.png"); LoadImage(MEMORY_SWAPPER,"assets/memory_swapper.png"); LoadImage(CORRUPTER,"assets/corrupter.png"); LoadImage(UNIT_ALLOCATOR,"assets/shell.png"); LoadImage(RAM_BANK,"assets/ram_bank.png"); LoadImage(RANGE_INDICATOR,"assets/range_indicator.png"); } bool VirusAttack::OnUserCreate(){ SetPixelMode(Pixel::MASK); game.Initialise(GetScreenSize()); InitializeImages(); IMAGES[MINIMAP_OUTLINE]=std::make_unique(); IMAGES[MINIMAP_OUTLINE]->Create(64,64); IMAGES[MATRIX]=std::make_unique(); IMAGES[MATRIX]->Create(64,64,false,false); IMAGES[MATRIX]->Sprite()->SetSampleMode(Sprite::PERIODIC); AL.AudioSystemInit(); AS_Test.AL = &AL; AS_Test.LoadAudioSample(0, "./assets/test.wav"); units.push_back(std::make_unique(vf2d{128,128},IMAGES,true)); units.push_back(std::make_unique(vf2d{129,129},IMAGES,true)); units.push_back(std::make_unique(vf2d{130,130},IMAGES,true)); units.push_back(std::make_unique(vf2d{130,140},IMAGES,true)); units.push_back(std::make_unique(vf2d{131,131},IMAGES,true)); units.push_back(std::make_unique(vf2d{132,132},IMAGES,true)); units.push_back(std::make_unique(vf2d{133,133},IMAGES,true)); units.push_back(std::make_unique(this,vf2d{134,134},IMAGES,true)); for(int i=0;i<5;i++){ collectionPoints.push_back(std::make_unique(this,vf2d{32.f+48*i,32.f},0,*IMAGES[MEMORY_COLLECTION_POINT],MemoryType(i))); collectionPoints.push_back(std::make_unique(this,vf2d{32.f,32.f+48*i},-PI/2,*IMAGES[MEMORY_COLLECTION_POINT],MemoryType(i))); } units.push_back(std::make_unique(this,vf2d{320,320},IMAGES,false)); units.push_back(std::make_unique(vf2d{360,300},IMAGES,false)); return true; } void VirusAttack::HandleDraggingSelection(){ auto NotClickingOnMinimap=[&](){return !(GetMouseX()>=ScreenWidth()-64&&GetMouseY()>=ScreenHeight()-64);}; if(GetMouse(0).bPressed){ if(NotClickingOnMinimap()){ for(auto&u:units){ u->Deselect(); } if(startingDragPos==CONSTANT::UNSELECTED){ startingDragPos=GetWorldMousePos(); } } } if(GetMouse(0).bReleased&&startingDragPos!=CONSTANT::UNSELECTED){ vf2d endDragPos=GetWorldMousePos(); if(endDragPos.x selectionRegion(startingDragPos,endDragPos-startingDragPos); for(auto&u:units){ if(u->IsFriendly()){ if(geom2d::overlaps(selectionRegion,u->GetPos())){ u->Select(); } } } startingDragPos=CONSTANT::UNSELECTED; } } vf2d VirusAttack::GetWorldMousePos(){ return game.ScreenToWorld(GetMousePos()); } void VirusAttack::DrawSelectionRectangle(){ if(startingDragPos!=CONSTANT::UNSELECTED){ game.FillRectDecal(startingDragPos,GetWorldMousePos()-startingDragPos,{255,255,0,128}); } } void VirusAttack::HandleRightClickMove(){ if (GetMouse(1).bHeld){ bool selectedTarget=false; for(auto&u:units){ geom2d::rect unitRegion(u->GetPos()-u->GetUnitSize()/2,u->GetUnitSize()); if(geom2d::overlaps(unitRegion,GetWorldMousePos())){ for(auto&u2:units){ if(&u!=&u2){ if(!u->IsFriendly()&&u2->IsFriendly()&&u2->IsSelected()&&u2->CanInteractWithEnemies()){ u2->SetTargetUnit(u); } else if(u->IsFriendly()&&u2->IsFriendly()&&u2->IsSelected()&&u2->CanInteractWithAllies()){ u2->SetTargetUnit(u); } } } selectedTarget=true; break; } } if(!selectedTarget){ for(auto&u:units){ if(u->IsFriendly()&&u->IsSelected()){ u->SetTargetLocation(GetWorldMousePos()); } } } } } void VirusAttack::CollisionChecking(std::shared_ptru,std::shared_ptru2){ if(u!=u2&&geom2d::overlaps(geom2d::circle(u->GetPos(),u->GetUnitSize().x/2),geom2d::circle(u2->GetPos(),u2->GetUnitSize().x/2))){ geom2d::linecollisionLine(u->GetPos(),u2->GetPos()); float maxDist=u->GetUnitSize().x/2+u2->GetUnitSize().x/2; float dist=maxDist-collisionLine.length(); vf2d dir=collisionLine.vector().norm(); if(u->IsMoveable()){ u->SetPos(u->GetPos()-dir*dist/2); } if(u2->IsMoveable()){ u2->SetPos(u2->GetPos()+dir*dist/2); } } } void VirusAttack::IdentifyClosestTarget(std::weak_ptr&closestUnit,float&closestDist,std::shared_ptru,std::shared_ptru2){ if(u==u2)return; bool canInteract; canInteract= (u->IsFriendly()&&u->CanInteractWithEnemies()&&!u2->IsFriendly())|| (u->IsFriendly()&&u->CanInteractWithAllies()&&u2->IsFriendly())|| (!u->IsFriendly()&&u->CanInteractWithEnemies()&&u2->IsFriendly())|| (!u->IsFriendly()&&u->CanInteractWithAllies()&&!u2->IsFriendly()); if(canInteract){ geom2d::lineunitLine(u->GetPos(),u2->GetPos()); if(unitLine.length()Sprite()->Size(),IMAGES[MINIMAP_HUD]->Decal(),{1,1}); vf2d minimapTL=GetScreenSize()-vf2d{64,64}; vi2d worldPixelSize=CONSTANT::WORLD_SIZE*CONSTANT::TILE_SIZE; vf2d viewingTilesPct=vf2d{float(ScreenWidth()),float(ScreenHeight())}/CONSTANT::TILE_SIZE/CONSTANT::WORLD_SIZE; SetDrawTarget(IMAGES[MINIMAP_OUTLINE]->Sprite()); Clear(BLANK); DrawRect((game.GetWorldOffset()/worldPixelSize*64),viewingTilesPct*64/game.GetWorldScale()); for(auto&u:units){ vf2d squareSize=u->GetUnitSize()/vf2d(CONSTANT::WORLD_SIZE); squareSize.x=std::round(std::max(1.f,squareSize.x)); squareSize.y=std::round(std::max(1.f,squareSize.y)); FillRect(u->GetGhostPos()/worldPixelSize*64-squareSize,squareSize*2,u->IsFriendly()?GREEN:RED); } IMAGES[MINIMAP_OUTLINE]->Decal()->Update(); SetDrawTarget(nullptr); DrawDecal(minimapTL,IMAGES[MINIMAP_OUTLINE]->Decal()); } void VirusAttack::HandlePanAndZoom(float fElapsedTime){ float speedScale=std::min(1.f,game.GetWorldScale().x); if(GetKey(A).bHeld/*||GetMouseX()<=CONSTANT::SCROLL_BOUNDARY*/){ game.MoveWorldOffset(vf2d{-256*fElapsedTime,0}/speedScale); } if(GetKey(W).bHeld/*||GetMouseY()<=CONSTANT::SCROLL_BOUNDARY*/){ game.MoveWorldOffset(vf2d{0,-256*fElapsedTime}/speedScale); } if(GetKey(S).bHeld/*||GetMouseY()>=ScreenHeight()-CONSTANT::SCROLL_BOUNDARY*/){ game.MoveWorldOffset(vf2d{0,256*fElapsedTime}/speedScale); } if(GetKey(D).bHeld/*||GetMouseX()>=ScreenWidth()-CONSTANT::SCROLL_BOUNDARY*/){ game.MoveWorldOffset(vf2d{256*fElapsedTime,0}/speedScale); } if(GetMouseWheel()>0){ if(game.GetWorldScale().x<2){ game.ZoomAtScreenPos(1.25,GetMousePos()); } } else if(GetMouseWheel()<0){ if(game.GetWorldScale().x>0.5){ game.ZoomAtScreenPos(0.75,GetMousePos()); } } } void VirusAttack::HandleMinimapClick(){ if(startingDragPos==CONSTANT::UNSELECTED&&GetMouse(0).bHeld&&GetMouseX()>=ScreenWidth()-64&&GetMouseY()>=ScreenHeight()-64){ vf2d minimapTL=GetScreenSize()-vf2d{64,64}; game.SetWorldOffset(vf2d(GetMousePos()-minimapTL)/64*CONSTANT::WORLD_SIZE*CONSTANT::TILE_SIZE-vf2d(GetScreenSize())/game.GetWorldScale()/2.f); } } void VirusAttack::UpdateMatrixTexture(float fElapsedTime){ 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(IMAGES[MATRIX]->Sprite()); Sprite*img=IMAGES[MATRIX]->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.rwidth-1){ Pixel rightCol=img->GetPixel(x+1,y); if(rightCol.rGetPixel(1,y)); } SetDrawTarget(nullptr); updatePixelsTimer=0.1; } if(activeLetters.size()>0){ SetDrawTarget(IMAGES[MATRIX]->Sprite()); for(Letter&letter:activeLetters){ letter.pos.y+=letter.spd*fElapsedTime; DrawString(letter.pos,std::string(1,letter.c)); } SetDrawTarget(nullptr); IMAGES[MATRIX]->Decal()->Update(); } matrixTimer=std::max(0.f,matrixTimer-fElapsedTime); updatePixelsTimer=std::max(0.f,updatePixelsTimer-fElapsedTime); std::erase_if(activeLetters,[](Letter&letter){return letter.pos.y<-32;}); } void VirusAttack::RenderCollectionPoints(CollectionPoint*cp){ geom2d::rectcpRect=geom2d::rect({cp->pos-cp->img.Sprite()->Size()/2,cp->img.Sprite()->Size()}); geom2d::rectviewRegion=geom2d::rect({game.GetWorldTL(),game.GetWorldVisibleArea()}); if(geom2d::overlaps(cpRect,viewRegion)){ Pixel col; switch(cp->type){ case HEALTH:{ col=CONSTANT::HEALTH_COLOR; }break; case RANGE:{ col=CONSTANT::RANGE_COLOR; }break; case ATKSPD:{ col=CONSTANT::ATKSPD_COLOR; }break; case MOVESPD:{ col=CONSTANT::MOVESPD_COLOR; }break; case PROCEDURE:{ col=CONSTANT::PROCEDURE_COLOR; }break; } game.DrawRotatedDecal(cp->pos,cp->img.Decal(),cp->rot,cp->img.Sprite()->Size()/2,{1,1},col); } } bool VirusAttack::OnUserUpdate(float fElapsedTime){ UpdateMatrixTexture(fElapsedTime); HandleDraggingSelection(); HandleRightClickMove(); HandlePanAndZoom(fElapsedTime); HandleMinimapClick(); if (GetKey(olc::Key::P).bPressed) AS_Test.Play(); for(auto&tile:TileManager::visibleTiles){ tile.second-=fElapsedTime; } std::erase_if(TileManager::visibleTiles,[](std::pair key){return key.second<=0;}); for(auto&u:units){ std::weak_ptrclosestUnit; float closestDist=999999; for(auto&u2:units){ IdentifyClosestTarget(closestUnit,closestDist,u,u2); CollisionChecking(u,u2); } if(u->IsFriendly()){ for(int y=-1;y<2;y++){ for(int x=-1;x<2;x++){ TileManager::visibleTiles[u->GetPos()/24/4+vi2d(x,y)]=5; } } } u->AttemptAttack(u,closestUnit,units); u->_Update(this); } std::erase_if(units,[&](std::shared_ptru){ if(u->GetHealth()==0){ deathAnimations.emplace_back(std::make_unique(this,u->GetPos(),u->GetImage(),*IMAGES[MATRIX],u->IsFriendly())); return true; } else { return false; } }); game.DrawPartialDecal({0,0},CONSTANT::WORLD_SIZE*CONSTANT::TILE_SIZE,IMAGES[TILE]->Decal(),{0,0},CONSTANT::WORLD_SIZE*CONSTANT::TILE_SIZE,DARK_GREEN); for(auto&u:units){ u->DrawRangeIndicator(this,game,IMAGES); } for(auto&u:units){ u->Draw(game,IMAGES); } for(auto&deadUnit:deathAnimations){ deadUnit->Update(fElapsedTime); deadUnit->Draw(game,this); } std::erase_if(deathAnimations,[](auto&u){return u->IsDone();}); for(auto&collectionPoint:collectionPoints){ collectionPoint->Update(this,*IMAGES[MATRIX]); RenderCollectionPoints(collectionPoint.get()); } for(auto&u:units){ u->DrawHud(game,IMAGES); } DrawSelectionRectangle(); RenderFogOfWar(); DrawMinimap(); std::sort(units.begin(),units.end(),[&](auto&u1,auto&u2){ float dist1=geom2d::line(u1->GetGhostPos(),GetWorldMousePos()).length(); float dist2=geom2d::line(u2->GetGhostPos(),GetWorldMousePos()).length(); return dist1>dist2;}); return true; } void VirusAttack::RenderFogOfWar(){ for(int y=game.GetTopLeftTile().y/96-1;y<=game.GetBottomRightTile().y/96+1;y++){ for(int x=game.GetTopLeftTile().x/96-1;x<=game.GetBottomRightTile().x/96+1;x++){ if(TileManager::visibleTiles.count(vi2d{x,y})==0){ if(x>=0&&y>=0&&x<=CONSTANT::WORLD_SIZE.x*CONSTANT::TILE_SIZE.x&&y<=CONSTANT::WORLD_SIZE.y*CONSTANT::TILE_SIZE.y){ game.FillRectDecal(vf2d{float(x),float(y)}*96,{96,96},{0,0,0,128}); } } } } } VirusAttack::CollectionPoint::CollectionPoint(PixelGameEngine*pge,vf2d pos,float rot,Renderable&collectionPointImg,MemoryType type) :pos(pos),rot(rot),type(type),originalCollectionPointImg(collectionPointImg.Sprite()),randomOffset({util::random(128),util::random(128)}){ img.Create(collectionPointImg.Sprite()->width,collectionPointImg.Sprite()->height); pge->SetDrawTarget(img.Sprite()); pge->Clear(BLANK); pge->DrawSprite({0,0},collectionPointImg.Sprite()); pge->SetDrawTarget(nullptr); img.Decal()->Update(); } void VirusAttack::CollectionPoint::Update(PixelGameEngine*pge,Renderable&matrixImg){ pge->SetDrawTarget(img.Sprite()); for(int y=0;yheight;y++){ for(int x=0;xwidth;x++){ Pixel col=originalCollectionPointImg->GetPixel(x,y); if(col==WHITE){ pge->Draw(x,y,matrixImg.Sprite()->GetPixel(int(x+randomOffset.x),int(y+randomOffset.y))); } } } img.Decal()->Update(); pge->SetDrawTarget(nullptr); } int main() { VirusAttack app; if (app.Construct(426, 320, 4, 4)) app.Start(); return 0; }