#include "Unit.h" #include "olcUTIL_Geometry2D.h" #include "TileManager.h" #include "util.h" #include "DebuffIcon.h" #include "olcPGEX_QuickGUI.h" #include "Textbox.h" Unit::~Unit(){}; void Unit::RandomHit(std::vector>&SOUNDS){ switch(rand()%3){ case 0:{SOUNDS[Sound::HIT1]->Play(GetPos(),1,0.6);}break; case 1:{SOUNDS[Sound::HIT2]->Play(GetPos(),1,0.6);}break; case 2:{SOUNDS[Sound::HIT3]->Play(GetPos(),1,0.6);}break; } } std::string LeftShifter::unitName="Left Shifter"; std::string LeftShifter::unitDescription="Shifts target memory 1 bit to the left."; std::vector LeftShifter::resourceCost={{RANGE,2},{ATKSPD,2},{MOVESPD,6},{PROCEDURE,1},{HEALTH,4}}; LeftShifter::LeftShifter(PixelGameEngine*pge,vf2d pos,std::vector>&IMAGES,bool friendly,bool moveable) :Unit(pge,LeftShifter::resourceCost,pos,12,*IMAGES[LEFT_SHIFTER],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,moveable){} void LeftShifter::Attack(Unit&victim,std::vector>&otherUnits ,std::vector>&SOUNDS){ victim<<=1; RandomHit(SOUNDS); } std::string RightShifter::unitName="Right Shifter"; std::string RightShifter::unitDescription="Shifts target memory 1 bit to the right."; std::vector RightShifter::resourceCost={{HEALTH,4},{RANGE,2},{ATKSPD,2},{MOVESPD,6},{PROCEDURE,1}}; RightShifter::RightShifter(PixelGameEngine*pge,vf2d pos,std::vector>&IMAGES,bool friendly,bool moveable) :Unit(pge,RightShifter::resourceCost,pos,12,*IMAGES[RIGHT_SHIFTER],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,moveable){} void RightShifter::Attack(Unit&victim,std::vector>&otherUnits ,std::vector>&SOUNDS){ victim>>=1; RandomHit(SOUNDS); } std::string BitRestorer::unitName="Bit Restorer"; std::string BitRestorer::unitDescription="Randomly restores 1 missing bit to a target."; std::vector BitRestorer::resourceCost={{PROCEDURE,6},{RANGE,1},{ATKSPD,1},{MOVESPD,2},{HEALTH,2}}; BitRestorer::BitRestorer(PixelGameEngine*pge,vf2d pos,std::vector>&IMAGES,bool friendly,bool moveable) :Unit(pge,BitRestorer::resourceCost,pos,12,*IMAGES[BIT_RESTORER],CONSTANT::HEALER_TARGET_COL,CONSTANT::HEALER_ATTACK_COL,friendly,moveable,true,false){} void BitRestorer::Attack(Unit&victim,std::vector>&otherUnits ,std::vector>&SOUNDS){ std::vectoremptyMemoryPositions; for(int i=0;iPlay(GetPos(),1,0.6); } void BitRestorer::AttemptToHealOtherAllies(std::vector>&otherUnits,std::vector>&SOUNDS){ std::vectoremptyMemoryPositions; for(auto&u:otherUnits){ if(u.get()!=this&&u->IsFriendly()&&InRange(u)){ for(int i=0;iGetMemorySize();i++){ if(!u->memory[i]){ emptyMemoryPositions.emplace_back(i); } } if(emptyMemoryPositions.size()!=0){ int randomBit=emptyMemoryPositions[rand()%emptyMemoryPositions.size()]; u->memory[randomBit]=true; appliedTarget=u; SOUNDS[Sound::HEAL]->Play(GetPos(),1,0.6); return; } } } } std::string MemorySwapper::unitName="Memory Swapper"; std::string MemorySwapper::unitDescription="Flips the orientation of all bits of a target around."; std::vector MemorySwapper::resourceCost={{RANGE,3},{ATKSPD,1},{HEALTH,3},{PROCEDURE,3},{MOVESPD,4}}; MemorySwapper::MemorySwapper(PixelGameEngine*pge,vf2d pos,std::vector>&IMAGES,bool friendly,bool moveable) :Unit(pge,MemorySwapper::resourceCost,pos,12,*IMAGES[MEMORY_SWAPPER],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,moveable,true){ autoAcquireFriendlyTarget=false; } void MemorySwapper::Attack(Unit&victim,std::vector>&otherUnits ,std::vector>&SOUNDS){ std::vectoroldMemory=victim.memory; for(int i=0;iorder; for(int i=0;i<5;i++){ int lowestInd=999999; Marker*lowest=nullptr; if(victim.health.indexindex;} if(victim.range.indexindex;} if(victim.atkSpd.indexindex;} if(victim.moveSpd.indexindex;} if(victim.procedure.indexindex;} if(lowest!=nullptr){ order.push_back(lowest); lowest->index=9999999; } } std::reverse(order.begin(),order.end()); int i=0; int marker=0; while(markerindex=marker; marker+=order[i]->size; i++; } //Clean up after ourselves for any 0-sized stats. if(victim.health.index==9999999)victim.health.index=0; if(victim.range.index==9999999)victim.range.index=0; if(victim.atkSpd.index==9999999)victim.atkSpd.index=0; if(victim.moveSpd.index==9999999)victim.moveSpd.index=0; if(victim.procedure.index==9999999)victim.procedure.index=0; RandomHit(SOUNDS); } std::string Corrupter::unitName="Corrupter"; std::string Corrupter::unitDescription="Chooses a random bit and negates it on a target."; std::vector Corrupter::resourceCost={{ATKSPD,3},{RANGE,1},{PROCEDURE,8},{MOVESPD,8},{HEALTH,4}}; Corrupter::Corrupter(PixelGameEngine*pge,vf2d pos,std::vector>&IMAGES,bool friendly,bool moveable) :Unit(pge,Corrupter::resourceCost,pos,12,*IMAGES[CORRUPTER],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,moveable){} void Corrupter::Attack(Unit&victim,std::vector>&otherUnits ,std::vector>&SOUNDS){ //Chooses a bit at random and corrupts it. int randomBit=rand()%victim.memory.size(); if(victim.memory[randomBit]){ RandomHit(SOUNDS); } victim.memory[randomBit]=false; } std::string MemoryAllocator::unitName="Memory Allocator"; std::string MemoryAllocator::unitDescription="A unit that builds other units."; std::vector MemoryAllocator::resourceCost={{RANGE,1},{ATKSPD,1},{MOVESPD,2},{PROCEDURE,1},{HEALTH,1}}; MemoryAllocator::MemoryAllocator(PixelGameEngine*pge,vf2d pos,std::vector>&IMAGES,bool friendly,bool moveable) :Unit(pge,MemoryAllocator::resourceCost,pos,12,*IMAGES[UNIT_ALLOCATOR],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,true,false){ isAllocator=true; } void MemoryAllocator::Attack(Unit&victim,std::vector>&otherUnits ,std::vector>&SOUNDS){ } void MemoryAllocator::Update(PixelGameEngine*pge,std::vector>&SOUNDS,std::vector>&queuedUnits){ if(IsBuilding()){ SetTargetLocation(CONSTANT::UNSELECTED); target.reset(); buildTime-=pge->GetElapsedTime(); if(buildTime<=0){ for(int i=0;iPlay(GetPos()); queuedUnits.push_back(std::move(buildTransformUnit)); } } } void MemoryAllocator::Draw(TileTransformedView&game,std::vector>&IMAGES){ if(IsBuilding()){ game.GetPGE()->SetDrawTarget(img.Sprite()); game.GetPGE()->Clear(BLANK); Renderable&targetImg=buildTransformUnit->GetImage(); game.GetPGE()->SetDrawTarget(nullptr); game.DrawPartialRotatedDecal(GetGhostPos(),img.Decal(),0,img.Sprite()->Size()/2,{0,0},{float(img.Sprite()->width),float((buildTime/CONSTANT::UNIT_BUILD_TIME)*img.Sprite()->height)},{1,1},GetUnitColor()/3); game.DrawPartialRotatedDecal(GetGhostPos()+vf2d{0.f,(buildTime/CONSTANT::UNIT_BUILD_TIME)*buildTransformUnit->GetImage().Sprite()->height},buildTransformUnit->GetImage().Decal(),0,buildTransformUnit->GetImage().Sprite()->Size()/2,{0.f,(buildTime/CONSTANT::UNIT_BUILD_TIME)*buildTransformUnit->GetImage().Sprite()->height},{float(buildTransformUnit->GetImage().Sprite()->width),float(buildTransformUnit->GetImage().Sprite()->height-(buildTime/CONSTANT::UNIT_BUILD_TIME)*buildTransformUnit->GetImage().Sprite()->height)},{1,1},GetUnitColor()/1.5f); if(fmod(buildTime,1)>0.5){ game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),{0, 202, 217}); } } else { game.DrawRotatedDecal(GetGhostPos(),img.Decal(),0,img.Sprite()->Size()/2,{1,1},GetUnitColor()); if(IsSelected()){ game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),WHITE); } if(IsAttached()){ game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),IsFriendly()?GREEN:RED); } } } std::string RAMBank::unitName="RAM Bank"; std::string RAMBank::unitDescription="Allows for the construction of Memory Allocators."; std::vector RAMBank::resourceCost={{PROCEDURE,25},{HEALTH,16}}; RAMBank::RAMBank(PixelGameEngine*pge,vf2d pos,std::vector>&IMAGES,bool friendly) :Unit(pge,RAMBank::resourceCost,pos,41,*IMAGES[RAM_BANK],WHITE,WHITE,friendly,false ,false,false ),randomOffset({util::random(128),util::random(128)}),matrixImg(*IMAGES[MATRIX]), originalImg(*IMAGES[RAM_BANK]){ img.Create(IMAGES[RAM_BANK]->Sprite()->width,IMAGES[RAM_BANK]->Sprite()->height); pge->SetDrawTarget(img.Sprite()); pge->Clear(BLANK); pge->DrawSprite({0,0},IMAGES[RAM_BANK]->Sprite()); pge->SetDrawTarget(nullptr); allocatorManager.colNormal = olc::VERY_DARK_GREEN; allocatorManager.colHover = olc::GREEN; allocatorManager.colClick = olc::YELLOW; allocatorManager.colDisable = olc::DARK_GREY; allocatorManager.colBorder = olc::YELLOW; allocatorManager.colText = olc::WHITE; allocatorButton = new QuickGUI::ImageButton(allocatorManager,*IMAGES[UNIT_ALLOCATOR],{0.5f,0.5f},pos-vf2d{8,48},{20,20}); isRAMBank=true; } void RAMBank::Attack(Unit&victim,std::vector>&otherUnits ,std::vector>&SOUNDS){ } void RAMBank::Update(PixelGameEngine*pge,std::vector>&SOUNDS,std::vector>&queuedUnits){ pge->SetDrawTarget(img.Sprite()); for(int y=0;yheight;y++){ for(int x=0;xwidth;x++){ Pixel col = originalImg.Sprite()->GetPixel(x,y); if(col==WHITE){ pge->Draw(x,y,matrixImg.Sprite()->GetPixel(int(x+randomOffset.x),int(y+randomOffset.y))); } } } if(!soundStarted){ soundStarted=true; soundHandle=SOUNDS[int(Sound::HUM)]->Play(GetPos(),0.4,0.4,true); } img.Decal()->Update(); pge->SetDrawTarget(nullptr); } void RAMBank::DrawHud(TileTransformedView&game,std::vector>&IMAGES){ if(IsSelected()){ allocatorManager.DrawDecal(game); } } void RAMBank::Draw(TileTransformedView&game,std::vector>&IMAGES){ game.DrawRotatedDecal(GetGhostPos(),img.Decal(),0,img.Sprite()->Size()/2,{1,1},GetUnitColor()); if(IsSelected()){ game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),WHITE); } if(IsAttached()){ game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),IsFriendly()?GREEN:RED); } } void RAMBank::OnDeath(std::vector>&SOUNDS){ SOUNDS[int(Sound::HUM)]->Stop(soundHandle); } void RAMBank::UpdateGUIState(TileTransformedView&game,Resources&player_resources,Textbox&displayBox,bool&hovered,int totalUsedMemory,int availableMemory){ allocatorManager.Update(game); auto TotalResources=[&](){ return player_resources.atkSpd+player_resources.health+player_resources.moveSpd+player_resources.procedure+player_resources.range; }; bool buttonEnabled=IsSelected()&&TotalResources()>=5&&GetProcedure()==procedure.size&&5+totalUsedMemory<=availableMemory; if(buttonEnabled&&allocatorButton->bHover){ displayBox.Initialize(CONSTANT::MEMORY_ALLOCATOR_BOX_DISPLAY_STRING,{},CONSTANT::MEMORY_ALLOCATOR_BOX_HEADER_STRING); //displayBox.SetVisible(true); hovered=true; } allocatorButton->Enable(buttonEnabled); } bool RAMBank::ClickHandled(TileTransformedView&game,Resources&player_resources,std::vector>&units,std::vector>&IMAGES){ if(allocatorButton->bPressed){ //First try to take one of each. if(player_resources.atkSpd>0&&player_resources.health>0&&player_resources.moveSpd>0&&player_resources.procedure>0&&player_resources.range>0){ player_resources.atkSpd--; player_resources.health--; player_resources.moveSpd--; player_resources.procedure--; player_resources.range--; } else { //Remove from the highest resource amount until 5 are removed. for(int i=0;i<5;i++){ int*maxAmt=&player_resources.atkSpd; if(player_resources.health>*maxAmt){ maxAmt=&player_resources.health; } if(player_resources.moveSpd>*maxAmt){ maxAmt=&player_resources.moveSpd; } if(player_resources.procedure>*maxAmt){ maxAmt=&player_resources.procedure; } if(player_resources.range>*maxAmt){ maxAmt=&player_resources.range; } (*maxAmt)--; } } units.push_back(std::make_shared(game.GetPGE(),GetPos()+vf2d{0,24},IMAGES,friendly)); return true; } return false; } std::string _Platform::unitName="Platform"; std::string _Platform::unitDescription="Anchored to the ground, this unit is an intermediate step for larger units."; std::vector _Platform::resourceCost={{HEALTH,6}}; _Platform::_Platform(PixelGameEngine*pge,vf2d pos,std::vector>&IMAGES,bool friendly,bool moveable) :Unit(pge,_Platform::resourceCost,pos,24,*IMAGES[PLATFORM],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,false){ isPlatform=true; } void _Platform::Attack(Unit&victim,std::vector>&otherUnits ,std::vector>&SOUNDS){}; void _Platform::Update(PixelGameEngine*pge,std::vector>&SOUNDS,std::vector>&queuedUnits){ if(IsBuilding()){ SetTargetLocation(CONSTANT::UNSELECTED); target.reset(); buildTime-=pge->GetElapsedTime(); if(buildTime<=0){ for(int i=0;iPlay(GetPos()); queuedUnits.push_back(std::move(buildTransformUnit)); } } } void _Platform::Draw(TileTransformedView&game,std::vector>&IMAGES){ if(IsBuilding()){ game.GetPGE()->SetDrawTarget(img.Sprite()); game.GetPGE()->Clear(BLANK); Renderable&targetImg=buildTransformUnit->GetImage(); game.GetPGE()->SetDrawTarget(nullptr); game.DrawPartialRotatedDecal(GetGhostPos(),img.Decal(),0,img.Sprite()->Size()/2,{0,0},{float(img.Sprite()->width),float((buildTime/CONSTANT::UNIT_BUILD_TIME)*img.Sprite()->height)},{1,1},GetUnitColor()/3); game.DrawPartialRotatedDecal(GetGhostPos()+vf2d{0.f,(buildTime/CONSTANT::UNIT_BUILD_TIME)*buildTransformUnit->GetImage().Sprite()->height},buildTransformUnit->GetImage().Decal(),0,buildTransformUnit->GetImage().Sprite()->Size()/2,{0.f,(buildTime/CONSTANT::UNIT_BUILD_TIME)*buildTransformUnit->GetImage().Sprite()->height},{float(buildTransformUnit->GetImage().Sprite()->width),float(buildTransformUnit->GetImage().Sprite()->height-(buildTime/CONSTANT::UNIT_BUILD_TIME)*buildTransformUnit->GetImage().Sprite()->height)},{1,1},GetUnitColor()/1.5f); if(fmod(buildTime,1)>0.5){ game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),{0, 202, 217}); } } else { game.DrawRotatedDecal(GetGhostPos(),img.Decal(),0,img.Sprite()->Size()/2,{1,1},GetUnitColor()); if(IsSelected()){ game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),WHITE); } if(IsAttached()){ game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),IsFriendly()?GREEN:RED); } } } std::string Refresher::unitName="Refresher"; std::string Refresher::unitDescription="Repairs missing bits to surrounding units."; std::vector Refresher::resourceCost={{ATKSPD,3},{RANGE,1},{PROCEDURE,8},{HEALTH,4}}; Refresher::Refresher(PixelGameEngine*pge,vf2d pos,std::vector>&IMAGES,bool friendly,bool moveable) :Unit(pge,Refresher::resourceCost,pos,24,*IMAGES[REFRESHER],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,false ,true,false){} void Refresher::Attack(Unit&victim,std::vector>&otherUnits ,std::vector>&SOUNDS){ target.reset(); //Doesn't acquire a target. for(auto&u:otherUnits){ if(IsFriendly()==u->IsFriendly()&&InRange(u.get())){ int maxAttempts=1000; int attempts=0; while(attemptsGetMemorySize(); if(!u->memory[randomBit]){ u->memory[randomBit]=true; return; } attempts++; } } } } void Refresher::OnDeath(std::vector>&SOUNDS){ SOUNDS[Sound::REFRESHER]->Stop(soundHandle); }; void Refresher::Update(PixelGameEngine*pge,std::vector>&SOUNDS,std::vector>&queuedUnits){ if(!soundStarted){ soundStarted=true; soundHandle=SOUNDS[Sound::REFRESHER]->Play(GetPos(),1,1,true); } }; std::string Turret::unitName="Turret"; std::string Turret::unitDescription="Automatically targets attack and movement speed memory ranges before others."; std::vector Turret::resourceCost={{ATKSPD,4},{RANGE,5},{HEALTH,6},{PROCEDURE,16}}; Turret::Turret(PixelGameEngine*pge,vf2d pos,std::vector>&IMAGES,bool friendly,bool moveable) :Unit(pge,Turret::resourceCost,pos,24,*IMAGES[TURRET],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,false){} void Turret::Attack(Unit&victim,std::vector>&otherUnits ,std::vector>&SOUNDS){ if(victim.GetMoveSpd()>0){ for(int i=0;i0){ for(int i=0;i>&SOUNDS){ SOUNDS[Sound::TURRET]->Stop(soundHandle); }; void Turret::Update(PixelGameEngine*pge,std::vector>&SOUNDS,std::vector>&queuedUnits){ if(!soundStarted){ soundStarted=true; soundHandle=SOUNDS[Sound::TURRET]->Play(GetPos(),1,1,true); } }; std::string MemoryGuard::unitName="Memory Guard"; std::string MemoryGuard::unitDescription="Reduces the chance of bit modification for all surrounding units by 30%"; std::vector MemoryGuard::resourceCost={{HEALTH,10},{ATKSPD,4},{RANGE,4},{PROCEDURE,12}}; MemoryGuard::MemoryGuard(PixelGameEngine*pge,vf2d pos,std::vector>&IMAGES,bool friendly,bool moveable) :Unit(pge,MemoryGuard::resourceCost,pos,24,*IMAGES[MEMORY_GUARD],CONSTANT::ATTACKER_TARGET_COL,CONSTANT::ATTACKER_ATTACK_COL,friendly,false ,true,false){} void MemoryGuard::Attack(Unit&victim,std::vector>&otherUnits ,std::vector>&SOUNDS){ target.reset(); //Doesn't acquire a target. for(auto&u:otherUnits){ if(IsFriendly()==u->IsFriendly()&&InRange(u.get())){ u->SetGuardTime(3); } } } void MemoryGuard::OnDeath(std::vector>&SOUNDS){ SOUNDS[Sound::MEMORY_GUARD]->Stop(soundHandle); }; void MemoryGuard::Update(PixelGameEngine*pge,std::vector>&SOUNDS,std::vector>&queuedUnits){ if(!soundStarted){ soundStarted=true; soundHandle=SOUNDS[Sound::MEMORY_GUARD]->Play(GetPos(),1,1,true); } }; Unit::Unit(PixelGameEngine*pge,std::vectormemory,vf2d pos,float radius,Renderable&img,Pixel targetLineColor,Pixel attackingLineColor,bool friendly,bool moveable,bool friendlyInteractable,bool enemyInteractable) :pos(pos),radius(radius),ghostPos({-999999,-999999}),img(img),targetLineCol(targetLineColor),attackingLineCol(attackingLineColor),friendly(friendly),moveable(moveable),friendlyInteractable(friendlyInteractable),enemyInteractable(enemyInteractable){ int marker=0; for(Memory&mem:memory){ for(int i=0;imemory.push_back(true); this->ghostMemory.push_back(true); } switch(mem.type){ case HEALTH:{ health.index=marker; health.size=mem.size; }break; case RANGE:{ range.index=marker; range.size=mem.size; }break; case ATKSPD:{ atkSpd.index=marker; atkSpd.size=mem.size; }break; case MOVESPD:{ moveSpd.index=marker; moveSpd.size=mem.size; }break; case PROCEDURE:{ procedure.index=marker; procedure.size=mem.size; }break; } marker+=mem.size; } attackingLine.Create(25,24,false,false); targetingLine.Create(25,24,false,false); } void Unit::DrawRangeIndicator(PixelGameEngine*pge,TileTransformedView&game,std::vector>&IMAGES){ if(!CanInteractWithAllies()&&!CanInteractWithEnemies())return; float dist=geom2d::line(game.ScreenToWorld(pge->GetMousePos()),GetGhostPos()).length(); float range=12*(GetRange()+1); float totalRange=GetUnitSize().x/2+range; if(IsSelected()){ Pixel col; if(CanInteractWithAllies()&&!CanInteractWithEnemies()){ col={40,127,173}; } else { col={0,196,0}; } game.DrawRotatedDecal(GetGhostPos(),IMAGES[RANGE_INDICATOR]->Decal(),0,IMAGES[RANGE_INDICATOR]->Sprite()->Size()/2,{totalRange/12,totalRange/12},col); }else if(distDecal(),0,IMAGES[RANGE_INDICATOR]->Sprite()->Size()/2,{totalRange/12,totalRange/12},{col.r,col.g,col.b,transparency}); } } void Unit::Draw(TileTransformedView&game,std::vector>&IMAGES){ game.DrawRotatedDecal(GetGhostPos(),img.Decal(),0,img.Sprite()->Size()/2,{1,1},GetUnitColor()); if(IsSelected()){ game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),WHITE); } if(IsAttached()){ game.DrawRotatedDecal(GetGhostPos(),IMAGES[SELECTION_CIRCLE]->Decal(),0,IMAGES[SELECTION_CIRCLE]->Sprite()->Size()/2,vf2d(img.Sprite()->Size())/IMAGES[SELECTION_CIRCLE]->Sprite()->Size(),IsFriendly()?GREEN:RED); } } void Unit::DrawHud(TileTransformedView&game,std::vector>&IMAGES){} void Unit::_DrawHud(TileTransformedView&game,std::vector>&IMAGES,bool unitMetersGreyedOut){ DrawHud(game,IMAGES); int initialBarX=ghostPos.x-GetMemorySize()/2*CONSTANT::BAR_SQUARE_SIZE.x-CONSTANT::BAR_SQUARE_SIZE.x/2; int initialBarY=ghostPos.y-CONSTANT::BAR_SQUARE_SIZE.y-img.Sprite()->height/2-2; Pixel col=0; auto CheckColor=[&](int i,Pixel&col){ if(health.index==i&&health.size>0){ col=CONSTANT::HEALTH_COLOR; } if(range.index==i&&range.size>0){ col=unitMetersGreyedOut?VERY_DARK_GREY:CONSTANT::RANGE_COLOR; } if(atkSpd.index==i&&atkSpd.size>0){ col=unitMetersGreyedOut?VERY_DARK_GREY:CONSTANT::ATKSPD_COLOR; } if(moveSpd.index==i&&moveSpd.size>0){ col=unitMetersGreyedOut?VERY_DARK_GREY:CONSTANT::MOVESPD_COLOR; } if(procedure.index==i&&procedure.size>0){ col=unitMetersGreyedOut?VERY_DARK_GREY:CONSTANT::PROCEDURE_COLOR; } }; if(GetHealth()>0){ game.FillRectDecal(vf2d{float(initialBarX)+health.index*CONSTANT::BAR_SQUARE_SIZE.x, float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*health.size-1,2},CONSTANT::HEALTH_COLOR); } if(GetAtkSpd()>0){ game.FillRectDecal(vf2d{float(initialBarX)+atkSpd.index*CONSTANT::BAR_SQUARE_SIZE.x, float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*atkSpd.size-1,2},unitMetersGreyedOut?VERY_DARK_GREY:CONSTANT::ATKSPD_COLOR); } if(GetMoveSpd()>0){ game.FillRectDecal(vf2d{float(initialBarX)+moveSpd.index*CONSTANT::BAR_SQUARE_SIZE.x, float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*moveSpd.size-1,2},unitMetersGreyedOut?VERY_DARK_GREY:CONSTANT::MOVESPD_COLOR); } if(GetProcedure()>0){ game.FillRectDecal(vf2d{float(initialBarX)+procedure.index*CONSTANT::BAR_SQUARE_SIZE.x, float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*procedure.size-1,2},unitMetersGreyedOut?VERY_DARK_GREY:CONSTANT::PROCEDURE_COLOR); } if(GetRange()>0){ game.FillRectDecal(vf2d{float(initialBarX)+range.index*CONSTANT::BAR_SQUARE_SIZE.x, float(initialBarY)}-vf2d{0,1},CONSTANT::BAR_SQUARE_SIZE+vf2d{CONSTANT::BAR_SQUARE_SIZE.x*range.size-1,2},unitMetersGreyedOut?VERY_DARK_GREY:CONSTANT::RANGE_COLOR); } for(int i=0;i>&IMAGES){ if(!InFogOfWar()){ if(!target.expired()){ geom2d::linelineToTarget(pos,target.lock()->pos); lineToTarget.start=lineToTarget.rpoint(GetUnitSize().x/2); lineToTarget.end=lineToTarget.rpoint(lineToTarget.length()-GetUnitSize().x/4); util::ApplyMatrixEffect(game.GetPGE(),targetingLine,*IMAGES[TARGETING_LINE],IMAGES[MATRIX]); game.DrawPartialRotatedDecal(lineToTarget.upoint(0.5),targetingLine.Decal(),lineToTarget.vector().polar().y,{lineToTarget.length()/2,12},{lineShift*10,0},{lineToTarget.length(),24},{1,1},targetLineCol); } else if(targetLoc!=CONSTANT::UNSELECTED){ geom2d::linelineToTarget(pos,targetLoc); lineToTarget.start=lineToTarget.rpoint(GetUnitSize().x/2); lineToTarget.end=lineToTarget.rpoint(lineToTarget.length()-GetUnitSize().x/4); util::ApplyMatrixEffect(game.GetPGE(),targetingLine,*IMAGES[TARGETING_LINE],IMAGES[MATRIX]); game.DrawPartialRotatedDecal(lineToTarget.upoint(0.5),targetingLine.Decal(),lineToTarget.vector().polar().y,{lineToTarget.length()/2,12},{lineShift*10,0},{lineToTarget.length(),24},{1,0.6},CONSTANT::MOVE_LINE_COL); } if(!appliedTarget.expired()){ geom2d::linelineToTarget(pos,appliedTarget.lock()->pos); lineToTarget.start=lineToTarget.rpoint(GetUnitSize().x/2); lineToTarget.end=lineToTarget.rpoint(lineToTarget.length()-GetUnitSize().x/4); if(reloadTimer>0){ util::ApplyMatrixEffect(game.GetPGE(),attackingLine,*IMAGES[ATTACKING_LINE],IMAGES[MATRIX]); float reloadSpd=1.f/(GetAtkSpd()/2.f); game.DrawPartialRotatedDecal(lineToTarget.upoint(0.5),attackingLine.Decal(),lineToTarget.vector().polar().y,{lineToTarget.length()/2,12},{lineShift*30,0},{lineToTarget.length(),24},{1,1+(attackFailed?-0.3f:(reloadTimer/reloadSpd)*0.25f)},attackFailed?Pixel{192,192,192,130}:Pixel{attackingLineCol.r,attackingLineCol.g,attackingLineCol.b,uint8_t(IsFriendly()?200:160)}); } } } float dist=geom2d::line(game.ScreenToWorld(pge->GetMousePos()),GetGhostPos()).length(); float range=12*(GetRange()+1); auto DrawStatDown=[&](Marker&maxStat,int currentStat,vf2d pos,vf2d downDisplayPos,Image img,uint8_t transparency=255){ if(maxStat.size>0){ if(currentStat!=maxStat.size){ game.DrawDecal(this->pos+pos,IMAGES[img]->Decal(),{1,1},currentStat==0?Pixel{192,64,64,transparency}:Pixel{255,255,255,transparency}); if(currentStat>0){ game.DrawDecal(this->pos+downDisplayPos,IMAGES[DOWN_ARROW]->Decal(),{1,1},{255,255,255,transparency}); } else { game.DrawDecal(this->pos+downDisplayPos,IMAGES[RED_X]->Decal(),{1,1},{255,255,255,transparency}); } } } }; if(IsSelected()){ DrawStatDown(atkSpd,GetAtkSpd(),{-48-8,-24},{-48+8,-18},RLD); DrawStatDown(moveSpd,GetMoveSpd(),{-48-8,8},{-36+8,2},SPD); DrawStatDown(this->range,GetRange(),{8,-24},{20,-18},RNG); DrawStatDown(procedure,GetProcedure(),{8,8},{26,2},PRC); }else if(distrange,GetRange(),{8,-24},{20,-18},RNG,transparency); DrawStatDown(procedure,GetProcedure(),{8,8},{26,2},PRC,transparency); } } int Unit::GetBits(Marker&m){ int activeBits=0; for(int i=0;i>&SOUNDS){ switch(rand()%3){ case 0:{ SOUNDS[Sound::DEAD1]->Play(GetPos()); }break; case 1:{ SOUNDS[Sound::DEAD2]->Play(GetPos()); }break; case 2:{ SOUNDS[Sound::DEAD3]->Play(GetPos()); }break; } OnDeath(SOUNDS); } void Unit::OnDeath(std::vector>&SOUNDS){} int Unit::GetHealth(){ return GetBits(health); } int Unit::GetRange(){ return GetBits(range); } int Unit::GetAtkSpd(){ return GetBits(atkSpd); } int Unit::GetMoveSpd(){ return GetBits(moveSpd); } int Unit::GetProcedure(){ return GetBits(procedure); } int Unit::GetMemorySize(){ return memory.size(); } void Unit::RunAI(PixelGameEngine*pge){} void Unit::_RunAI(PixelGameEngine*pge){ RunAI(pge); } void Unit::_Update(PixelGameEngine*pge,std::vector>&SOUNDS,Resources&player_resources,Resources&enemy_resources,std::vector>&queuedUnits,std::array&resourceGainTimer,std::vector&resourceGainIcons,std::vector>&IMAGES,GameFlags&flags){ if(!target.expired()){ auto ptrTarget=target.lock(); if(!InRange(ptrTarget)&&CanMove()){ SetPos(GetPos()+(ptrTarget->GetPos()-pos).norm()*GetMoveSpd()*12*pge->GetElapsedTime()); } } else if(targetLoc!=CONSTANT::UNSELECTED){ float dist=geom2d::line(pos,targetLoc).length(); if(dist>24){ if(CanMove()){ SetPos(GetPos()+(targetLoc-pos).norm()*GetMoveSpd()*12*pge->GetElapsedTime()); } } else { if(willAttachWhenReachingDestination&&!attachTarget.expired()&&attachTarget.lock()->attachedUnit.expired()){ attachedPoint=attachTarget; attachedPoint.lock()->attachedUnit=self_ptr; } willAttachWhenReachingDestination=false; targetLoc=CONSTANT::UNSELECTED; } } if(!attachedPoint.expired()){ SetPos(attachedPoint.lock()->pos+vf2d{cos(float(attachedPoint.lock()->rot+PI/2))*16,sin(float(attachedPoint.lock()->rot+PI/2))*16}); collectionTime-=pge->GetElapsedTime(); if(collectionTime<=0){ collectionTime=CONSTANT::COLLECTION_WAIT_TIME; Resources&targetResource=IsFriendly()?player_resources:enemy_resources; switch(attachedPoint.lock()->type){ case HEALTH:{ if(!IsFriendly()&&flags.difficulty==2){ targetResource.health+=3; } if(!IsFriendly()&&flags.difficulty==1){ targetResource.health++; } if(!IsFriendly()&&flags.difficulty==0){ targetResource.health--; } targetResource.health++; }break; case RANGE:{ if(!IsFriendly()&&flags.difficulty==2){ targetResource.range+=3; } if(!IsFriendly()&&flags.difficulty==1){ targetResource.range++; } if(!IsFriendly()&&flags.difficulty==0){ targetResource.range--; } targetResource.range++; }break; case ATKSPD:{ if(!IsFriendly()&&flags.difficulty==2){ targetResource.atkSpd+=3; } if(!IsFriendly()&&flags.difficulty==1){ targetResource.atkSpd++; } if(!IsFriendly()&&flags.difficulty==0){ targetResource.atkSpd--; } targetResource.atkSpd++; }break; case MOVESPD:{ if(!IsFriendly()&&flags.difficulty==2){ targetResource.moveSpd+=3; } if(!IsFriendly()&&flags.difficulty==1){ targetResource.moveSpd++; } if(!IsFriendly()&&flags.difficulty==0){ targetResource.moveSpd--; } targetResource.moveSpd++; }break; case PROCEDURE:{ if(!IsFriendly()&&flags.difficulty==2){ targetResource.procedure+=3; } if(!IsFriendly()&&flags.difficulty==1){ targetResource.procedure++; } if(!IsFriendly()&&flags.difficulty==0){ targetResource.procedure--; } targetResource.procedure++; }break; } if(!InFogOfWar()){ resourceGainIcons.push_back({IMAGES[RESOURCE].get(),attachedPoint.lock()->type,GetPos()}); } } } if(!IsFriendly()){ _RunAI(pge); } if(!GhostInFogOfWar()&&InFogOfWar()){ HideGhost(); } if(!InFogOfWar()){ ghostPos=pos; ghostMemory=memory; } reloadTimer=std::max(0.f,reloadTimer-pge->GetElapsedTime()); guardTime=std::max(0.f,guardTime-pge->GetElapsedTime()); lineShift-=pge->GetElapsedTime(); if(lineShift<-25)lineShift+=25; Update(pge,SOUNDS,queuedUnits); } std::vector operator <<(Unit&u,const int n){ std::vectortempMem=u.memory; for(int i=0;i operator >>(Unit&u,const int n){ std::vectortempMem=u.memory; for(int i=1;itarget){ this->target=target; this->targetLoc=CONSTANT::UNSELECTED; willAttachWhenReachingDestination=false; } void Unit::SetTargetLocation(vf2d targetLoc){ this->target.reset(); this->targetLoc=targetLoc; willAttachWhenReachingDestination=false; } bool Unit::InRange(std::shared_ptrtarget){ return InRange(target.get()); } bool Unit::InRange(Unit*target){ float range=12*(GetRange()+1); float totalRange=GetUnitSize().x/2+range; return geom2d::overlaps(geom2d::circle{GetPos(),totalRange},geom2d::circle{target->GetPos(),target->GetUnitSize().x/2}); } bool Unit::InRange(vf2d pos){ float range=12*(GetRange()+1); float totalRange=GetUnitSize().x/2+range; return geom2d::overlaps(geom2d::circle{GetPos(),totalRange},geom2d::circle{pos,0.1}); } void Unit::SetPos(vf2d newPos){ pos=newPos; } void Unit::AttemptAttack(std::weak_ptrattacker,std::weak_ptrunit,std::vector>&otherUnits,std::vector&debuffIcons,std::vector>&IMAGES,std::vector>&SOUNDS){ if(reloadTimer>0)return; std::weak_ptrfinalTarget; if(!unit.expired()){ finalTarget=unit; if(!target.expired()){ auto ptrTarget=target.lock(); if(InRange(ptrTarget)){ finalTarget=ptrTarget; } } } if(!finalTarget.expired()){ if(InRange(finalTarget.lock())){ appliedTarget=finalTarget; bool hadAtkSpd=finalTarget.lock()->GetAtkSpd()>0; bool hadMoveSpd=finalTarget.lock()->GetMoveSpd()>0; bool hadRange=finalTarget.lock()->GetRange()>0; bool hadProcedure=finalTarget.lock()->GetProcedure()>0; _Attack(attacker,finalTarget,otherUnits,SOUNDS); //Call the parent function first, followed by the child. if(hadAtkSpd&&finalTarget.lock()->GetAtkSpd()==0){ debuffIcons.emplace_back(IMAGES[RLD_ICON].get(),IMAGES[RED_X].get(),finalTarget.lock()->GetPos()-vf2d{util::random(12)-6,4}); } if(hadMoveSpd&&finalTarget.lock()->GetMoveSpd()==0){ debuffIcons.emplace_back(IMAGES[SPD_ICON].get(),IMAGES[RED_X].get(),finalTarget.lock()->GetPos()-vf2d{util::random(12)-6,4}); } if(hadRange&&finalTarget.lock()->GetRange()==0){ debuffIcons.emplace_back(IMAGES[RNG_ICON].get(),IMAGES[RED_X].get(),finalTarget.lock()->GetPos()-vf2d{util::random(12)-6,4}); } if(hadProcedure&&finalTarget.lock()->GetProcedure()==0){ debuffIcons.emplace_back(IMAGES[PRC_ICON].get(),IMAGES[RED_X].get(),finalTarget.lock()->GetPos()-vf2d{util::random(12)-6,4}); } } } } void Unit::Update(PixelGameEngine*pge,std::vector>&SOUNDS,std::vector>&queuedUnits){} void Unit::Attacked(std::weak_ptrattacker){} void Unit::_Attacked(std::weak_ptrattacker,std::vector>&otherUnits){ Attacked(attacker); if(attacker.lock()->IsFriendly()!=IsFriendly()&&CanInteractWithEnemies()){ SetTargetUnit(attacker); } if(!IsFriendly()&&!attacker.lock()->IsFriendly()&&!attacker.lock()->CanInteractWithAllies()){ attacker.lock()->SetTargetLocation(attacker.lock()->GetPos()); //This effectively negates enemies targeting each other for attacking if they're not supposed to. Somehow this occurs sometimes. } if(attacker.lock()->IsFriendly()!=IsFriendly()){ for(auto&u:otherUnits){ if(this!=u.get()&&!u->IsFriendly()&&u->GetCurrentTarget().expired()&&u->CanMove()&&!u->IsAllocator()){ geom2d::linedistLine={GetPos(),u->GetPos()}; if(distLine.length()<320){ if(u->CanInteractWithEnemies()){ u->SetTargetUnit(attacker); }else if(u->CanInteractWithAllies()){ u->SetTargetLocation(GetPos()); } } } } } } void Unit::_Attack(std::weak_ptrattacker,std::weak_ptrfinalTarget,std::vector>&otherUnits,std::vector>&SOUNDS){ if(GetProcedure()>0&&GetAtkSpd()>0){ attackFailed=false; float procChance=float(GetProcedure())/procedure.size; if(util::random(1)>=1-procChance){ Attack(*finalTarget.lock(),otherUnits,SOUNDS); finalTarget.lock()->_Attacked(attacker,otherUnits); reloadTimer=1.f/(GetAtkSpd()/2.f); if(GetCurrentTarget().expired()&&!IsFriendly()){ if(finalTarget.lock()->IsFriendly()!=IsFriendly()&&CanInteractWithEnemies()){ SetTargetUnit(finalTarget); } } } else { reloadTimer=1.f/(GetAtkSpd()/2.f); attackFailed=true; } } } bool Unit::InFogOfWar(){ return TileManager::visibleTiles.count(GetPos()/96)==0; } bool Unit::GhostInFogOfWar(){ return TileManager::visibleTiles.count(ghostPos/96)==0; } void Unit::HideGhost(){ ghostPos={99999,-99999}; } vf2d Unit::GetGhostPos(){ return ghostPos; } bool Unit::IsMoveable(){ return moveable&&attachedPoint.expired(); } bool Unit::CanInteractWithAllies(){ return friendlyInteractable&&attachedPoint.expired()&&buildTime<=0; } bool Unit::CanInteractWithEnemies(){ return enemyInteractable&&attachedPoint.expired()&&buildTime<=0; } Renderable&Unit::GetImage(){ return img; } std::weak_ptrUnit::GetCurrentTarget(){ return target; } bool Unit::AutoAcquiresFriendlyTargets(){ return autoAcquireFriendlyTarget; } bool Unit::CanMove(){ return moveSpd.size>0&&attachedPoint.expired()&&buildTime<=0; } void Unit::SetTargetCollectionPoint(std::weak_ptrtargetCP,std::weak_ptrself_ptr){ SetTargetLocation(targetCP.lock()->pos); attachTarget=targetCP; willAttachWhenReachingDestination=true; this->self_ptr=self_ptr; } Pixel Unit::GetUnitColor(){ Pixel col; if(!attachedPoint.expired()){ switch(attachedPoint.lock()->type){ case HEALTH:{ return CONSTANT::HEALTH_COLOR/2; }break; case RANGE:{ return CONSTANT::RANGE_COLOR/2; }break; case ATKSPD:{ return CONSTANT::ATKSPD_COLOR/2; }break; case MOVESPD:{ return CONSTANT::MOVESPD_COLOR/2; }break; case PROCEDURE:{ return CONSTANT::PROCEDURE_COLOR/2; }break; } } else { return friendly?Pixel{192,192,255}:Pixel{255,192,192}; } } bool Unit::ClickHandled(TileTransformedView&game,Resources&player_resources,std::vector>&units,std::vector>&IMAGES){ return false; }; void Unit::UpdateGUIState(TileTransformedView&game,Resources&player_resources,Textbox&displayBox,bool&hovered,int totalUsedMemory,int availableMemory){}; bool Unit::IsAllocator(){ return isAllocator&&attachedPoint.expired()&&buildTime<=0; } void Unit::SetBuildUnit(float buildTime,std::shared_ptrfinalUnit,std::vector>&SOUNDS){ if(this->IsAllocator()){ SOUNDS[Sound::SMALLBUILD]->Play(GetPos()); } else if(this->IsPlatform()){ SOUNDS[Sound::BIGBUILD]->Play(GetPos()); } //Once the units start building they aren't considered allocators/platforms anymore, must do it before. this->buildTime=buildTime; this->buildTransformUnit=std::move(finalUnit); } bool Unit::IsBuilding(){ return buildTime>0; } void Unit::SetGuardTime(float time){ guardTime=time; } bool Unit::IsGuarded(){ return guardTime>0; } void Unit::SaveMemory(){ savedMemory=memory; } bool Unit::IsPlatform(){ return isPlatform&&attachedPoint.expired()&&buildTime<=0; } bool Unit::IsAttached(){ return !attachedPoint.expired(); } Unit*Unit::GetBuildUnit(){ return buildTransformUnit.get(); } bool Unit::IsRAMBank(){ return isRAMBank; }