diff --git a/planet/Defense.ocf/Defense.ocd/Homebase.ocd/Script.c b/planet/Defense.ocf/Defense.ocd/Homebase.ocd/Script.c index 8814bfa00..a7f8d2853 100644 --- a/planet/Defense.ocf/Defense.ocd/Homebase.ocd/Script.c +++ b/planet/Defense.ocf/Defense.ocd/Homebase.ocd/Script.c @@ -121,6 +121,14 @@ public func UpdateIndexedItem(int index) return false; } +public func GetEntryByID(def id) +{ + for (var i = 0; i < GetLength(base_material); i++) + if (base_material[i].item == id) + return i; + return nil; +} + public func GetEntryInformation(int entry_idx) { // Fill with current information for this entry @@ -269,6 +277,18 @@ public func SellItem(item) return success; } +// Makes an item available even though the requirements aren't yet met +public func SetItemAvailable(int entry_idx) +{ + // Safety + var entry = base_material[entry_idx]; + if (!entry) return false; + + entry.requirements = nil; + entry.cost = nil; + return true; +} + public func OnOwnerChanged(new_owner) { if (buy_menu) buy_menu->SetOwner(new_owner); diff --git a/planet/Defense.ocf/Windmill.ocs/Attack.ogg b/planet/Defense.ocf/Windmill.ocs/Attack.ogg new file mode 100644 index 000000000..8b37617ef Binary files /dev/null and b/planet/Defense.ocf/Windmill.ocs/Attack.ogg differ diff --git a/planet/Defense.ocf/Windmill.ocs/BigBoomattack.ocd/Script.c b/planet/Defense.ocf/Windmill.ocs/BigBoomattack.ocd/Script.c index a2b138330..fae5aad96 100644 --- a/planet/Defense.ocf/Windmill.ocs/BigBoomattack.ocd/Script.c +++ b/planet/Defense.ocf/Windmill.ocs/BigBoomattack.ocd/Script.c @@ -5,8 +5,8 @@ An evil rocket which is hungry on the destruction of windmills --*/ -#include Boomattack +/* local hits; protected func Construction() @@ -68,7 +68,7 @@ public func OnProjectileHit(object shot) } /* Contact */ - +/* protected func ContactBottom() { DoDrill(180); } protected func ContactTop() { DoDrill(0); } protected func ContactLeft() { DoDrill(-40); } @@ -109,3 +109,4 @@ local ActMap = { Hgt = 100 }, }; +*/ \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/Boomattack.ocd/DefCore.txt b/planet/Defense.ocf/Windmill.ocs/Boomattack.ocd/DefCore.txt index 60c76a634..f0953967b 100644 --- a/planet/Defense.ocf/Windmill.ocs/Boomattack.ocd/DefCore.txt +++ b/planet/Defense.ocf/Windmill.ocs/Boomattack.ocd/DefCore.txt @@ -1,7 +1,7 @@ [DefCore] id=Boomattack -Version=6,0 -Category=C4D_Object +Version=7,0 +Category=C4D_Vehicle ContactCalls=1 Width=15 Height=27 @@ -14,3 +14,4 @@ VertexFriction=80,60,60,60 Rotate=1 StretchGrowth=1 Oversize=1 +IncompleteActivity=1 \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/Boomattack.ocd/Script.c b/planet/Defense.ocf/Windmill.ocs/Boomattack.ocd/Script.c index 83d277fd7..cedf8fced 100644 --- a/planet/Defense.ocf/Windmill.ocs/Boomattack.ocd/Script.c +++ b/planet/Defense.ocf/Windmill.ocs/Boomattack.ocd/Script.c @@ -1,135 +1,133 @@ /*-- Boom attack - Authors: Randrian, Newton + Authors: Randrian, Newton, Sven2 - An evil rocket which is hungry on the destruction of windmills + An evil rocket which is hungry on the destruction of Windmills --*/ -local fuel; -protected func Construction() +/* Init */ + +public func Construction() { - //flight length - fuel=1000; + SetAction("Fly"); + SetComDir(COMD_None); + var fx = AddEffect("Flight", this, 150, 10, this); + fx.target = GetRandomWindmill(); + FxFlightTimer(this, fx, 0); + AddEffect("FlightRotation", this, 151, 1, this); + return true; } +/* Flight */ -protected func FxFlightTimer(object pTarget, effect, int iEffectTime) +local rotation = 0; + +private func FxFlightRotationTimer(object _this, effect, int time) { - if(fuel<=0) - { - DoFireworks(); - return; - } + if (rider) return FX_Execute_Kill; - var ignition = iEffectTime % 10; - - if(!ignition) + rotation += 2; + if (rotation > 360) rotation = 0; + + this.MeshTransformation = Trans_Rotate(rotation, 0,1); + return FX_OK; +} + +private func FxFlightTimer(object _this, effect, int time) +{ + // Attack! + if (!effect.target) { - var angle = GetR()+RandomX(-3,3); - SetXDir(Sin(angle,100), 100); - SetYDir(-Cos(angle,100), 100); + if (g_lost) DoFireworks(NO_OWNER); + effect.target = GetRandomWindmill(); + } + if(!(time % 10)) + { + // Adjust angle + var dx = effect.target->GetX() - GetX(), dy = effect.target->GetY()+50 - GetY(); + var aim_dist = 600; // at this distance, fly horizontally. when getting closer, gradually turn to direct flight into target + var aim_dy = dy * (aim_dist - Abs(dx)) / aim_dist; + var angle = Angle(0,0,dx,aim_dy); + SetXDir(Sin(angle, FlySpeed), 100); + SetYDir(-Cos(angle, FlySpeed), 100); SetR(angle); } - - if(GetAction() != "Fly") - { - SetAction("Fly"); - SetComDir(COMD_None); - } - + var x = -Sin(GetR(), 15); var y = +Cos(GetR(), 15); var xdir = GetXDir() / 2; var ydir = GetYDir() / 2; CreateParticle("FireDense", x, y, PV_Random(xdir - 4, xdir + 4), PV_Random(ydir - 4, ydir + 4), PV_Random(16, 38), Particles_Thrust(), 5); - - - fuel--; + + return FX_OK; } -public func IsProjectileTarget(target,shooter) +/* Riding */ + +local riderattach; +local rider; + +public func OnMount(clonk) { - if(target->GetID() == GetID()) - { - return false; - } + rider = clonk; + var iDir = -1; + if (GetX() > LandscapeWidth()/2) iDir = 1; + clonk->PlayAnimation("PosRocket", CLONK_ANIM_SLOT_Arms, Anim_Const(0), Anim_Const(1000)); + riderattach = AttachMesh(clonk, "main", "pos_tool1", Trans_Mul(Trans_Translate(-1000,2000*iDir,2000))); + return true; } -public func OnProjectileHit(object shot) +public func OnUnmount(clonk) { - var gol = FindObject(Find_ID(Goal_SaveTheWindmills)); - if(gol) gol->IncShotScore(shot->GetController()); - DoFireworks(); - return 1; + clonk->StopAnimation(clonk->GetRootAnimation(10)); + DetachMesh(riderattach); + return true; } -/* Contact */ +/* Contact / Explosion */ -protected func ContactBottom() { return Hit(); } -protected func ContactTop() { return Hit(); } -protected func ContactLeft() { return Hit(); } -protected func ContactRight() { return Hit(); } +public func IsProjectileTarget(target,shooter) { return true; } +public func OnProjectileHit(object shot) { return DoFireworks(shot->GetController()); } -protected func Hit() -{ - //Message("I have hit something"); - if(GetEffect("Flight",this)) DoFireworks(); - else Sound("Hits::Materials::Wood::WoodHit*"); -} - -protected func HitObject() -{ - DoFireworks(); -} - -func Launch(int angle) -{ - SetProperty("Collectible",0); - SetCategory(C4D_Vehicle); - SetAction("Fly"); - SetComDir(COMD_None); - - Exit(); - AddEffect("Flight",this,150,1,this); - //AddEffect("HitCheck", this, 1,1, nil,nil, 0, 0); - - SetR(angle); -} - -func DoFireworks(int speed) +public func ContactBottom() { return Hit(); } +public func ContactTop() { return Hit(); } +public func ContactLeft() { return Hit(); } +public func ContactRight() { return Hit(); } + +public func Hit() { return DoFireworks(NO_OWNER); } +public func HitObject() { return DoFireworks(NO_OWNER); } + +private func DoFireworks(int killed_by) { + if (rider) { + rider->SetAction("Walk"); + rider->Fling(RandomX(-5,5), -5); + } + GameCallEx("OnClonkDeath", this, killed_by); // for reward RemoveEffect("Flight",this); Fireworks(); Explode(40); + return true; } -func SetFuel(int new) -{ - fuel = new; -} +/* Status */ -func GetFuel() -{ - return fuel; -} +public func IsFlyingEnemy() { return true; } local ActMap = { - -Fly = { - Prototype = Action, - Name = "Fly", - Procedure = DFA_FLOAT, - Length = 1, - Delay = 0, - Wdt = 15, - Hgt = 27 -}, + Fly = { + Prototype = Action, + Name = "Fly", + Procedure = DFA_FLOAT, + Length = 1, + Delay = 0, + Wdt = 15, + Hgt = 27, + }, }; -local PerspectiveR = 20000; -local PerspectiveTheta = 25; -local PerspectivePhi = 30; -local Name = "$Name$"; -local Collectible = 1; + +local FlySpeed = 100; +local Name = "$Name$"; \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/CustomAI.ocd/DefCore.txt b/planet/Defense.ocf/Windmill.ocs/CustomAI.ocd/DefCore.txt new file mode 100644 index 000000000..9ab1cbb52 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/CustomAI.ocd/DefCore.txt @@ -0,0 +1,8 @@ +[DefCore] +id=CustomAI +Version=7 +Category=C4D_Vehicle | C4D_MouseIgnore +Width=1 +Height=1 +Mass=1 + diff --git a/planet/Defense.ocf/Windmill.ocs/CustomAI.ocd/Script.c b/planet/Defense.ocf/Windmill.ocs/CustomAI.ocd/Script.c new file mode 100644 index 000000000..28a60bcf1 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/CustomAI.ocd/Script.c @@ -0,0 +1,554 @@ +#include AI + +// How far must someone be away from an airship to be independent from it +local AIRSHIP_LOST_DIST = 50; +// How near must an airship be to the target to dispatch its troops +local AIRSHIP_BOARD_DIST = 60; +// How near must neighbouring airships be to trigger evasive maneuvers +local AIRSHIP_VICINITY_DIST = 60; +// How near must an archer be to a target to shoot. Theoretically, archers can shoot from much farer away but that's too brutal for the players +local ARCHER_SHOOT_DIST = 500; + +func AddAI(object clonk) +{ + var fx = AI->AddAI(clonk); + if (fx) + { + clonk.ExecuteAI = CustomAI.Execute; + fx.ai = CustomAI; + fx.ignore_allies = true; + } + return fx; +} + +func SetEnemyData(object clonk, proplist data) +{ + var fx = GetEffect("AI", clonk); + if (fx) + { + if (data.Siege) fx.is_siege = true; + return true; + } + return false; +} + +func FindTarget(fx, bool parent) +{ + if (parent) return _inherited(fx); + return this->GetNearestWindmill(); +} + +private func FindInventoryWeapon(fx) +{ + // Extra weapons + if (fx.weapon = FindContents(Axe)) + { fx.strategy = fx.ai.ExecuteMelee; return true; } + if (fx.vehicle && fx.vehicle->GetID() == Boomattack) + { fx.weapon = FindContents(Bow); fx.strategy = fx.ai.ExecuteRanged; fx.projectile_speed = 100; fx.aim_wait = 0; fx.ammo_check = fx.ai.HasArrows; fx.ranged=true; return true; } + if (inherited(fx, ...)) return true; + // no weapon :( + return false; +} + +func Execute(proplist fx, int time) +{ + if (!fx.target) fx.target = GetNearestWindmill(); + if (GetAction() == "Ride") + { + var action_target = GetActionTarget(); + if (action_target && action_target->GetID() == Balloon) + return ExecuteIdle(); // do nothing if hanging from a balloon + // else do everything! (action_target == Boomattack) + } + if (fx.parachute_lost) + { + if (GetAction() != "Walk") + return inherited(fx, time, ...); + fx.target = nil; + fx.parachute_lost = nil; + SetCommand(); + } + + return inherited(fx, time, ...); +} + +private func ExecuteVehicle(fx) +{ + if (!fx.vehicle) return false; + if (fx.vehicle->GetID() == Catapult) return _inherited(fx, ...); + if (fx.vehicle->GetID() == Airship) return ExecutePilot(fx); + return false; +} + +private func ExecutePilot(fx) // this function name doesn't sound right +{ + var target = fx.target ?? GetNearestWindmill(); + if (!target) return false; // ??? + + if (GetProcedure() != "PUSH" || GetActionTarget() != fx.vehicle) + { + if (ObjectDistance(fx.vehicle) > CustomAI.AIRSHIP_LOST_DIST) // We lost the airship (maybe we were flung from it) + { + fx.strategy = nil; + fx.weapon = nil; + fx.vehicle->PromoteNewCaptain(); + fx.vehicle = nil; + return true; + } + if (!GetCommand() || !Random(4)) SetCommand("Grab", fx.vehicle); + return true; + } + + // Close to the target + if (ObjectDistance(target) < CustomAI.AIRSHIP_BOARD_DIST) + { + fx.vehicle->PrepareToBoard(this); + return true; + } + + // No command check + if (!fx.vehicle->GetCommand()) + { + var point = this->GetBoardingPoint(target); + fx.vehicle->SetCommand("MoveTo", nil, point[0], point[1]); + fx.target = FindTarget(fx); + // Unmovable check + } else if (fx.vehicle->GetXDir() == 0 && fx.vehicle->GetYDir() == 0) + { + fx.stuck_time++; + if (fx.stuck_time > 20) + fx.vehicle->SetYDir(10); + if (fx.stuck_time > 40) + fx.vehicle->SetXDir(RandomX(-10,10)); + if (fx.stuck_time > 60) + { + if(InsideIslandRectangle(fx.vehicle)) + return fx.vehicle->PrepareToBoard(this); + else + fx.vehicle->Sink(); + } + } else if(fx.stuck_time) + { + fx.stuck_time = 0; + } + + // Vicinity check + var ships = FindObjects(Find_Distance(CustomAI.AIRSHIP_VICINITY_DIST), Find_ID(Airship), Find_Exclude(fx.vehicle)); + if (GetLength(ships) && !InsideIslandRectangle(fx.vehicle)) + { + var centerX = 0, centerY = 0; + for (var ship in ships) + { + centerX += ship->GetX(); + centerY += ship->GetY(); + } + centerX += fx.vehicle->GetX(); + centerY += fx.vehicle->GetY(); + centerX /= GetLength(ships) + 1; + centerY /= GetLength(ships) + 1; + // Move away from mass center + var targetX = fx.vehicle->GetX() - centerX; + var targetY = fx.vehicle->GetY() - centerY; + fx.vehicle->SetCommand("MoveTo", nil, BoundBy(GetX() + targetX * 2, 30, LandscapeWidth()-30), BoundBy(GetY() + targetY * 2, 30, LandscapeHeight()-30)); + } + + return true; +} + +private func CheckVehicleAmmo(fx, object catapult) +{ + // Ammo is auto-refilled + return true; +} + +func ExecuteIdle(proplist fx) +{ + // Idle execution overridden by Execute + return true; +} + +// Wait until airship arrives +func ExecuteMelee(fx) +{ + // Don't attack if still on the carrier + if (fx.carrier) + { + if (ObjectDistance(fx.carrier) > CustomAI.AIRSHIP_LOST_DIST) fx.carrier = nil; + if (GetCommand()) SetCommand("None"); + if (fx.shield) return ExecuteProtection(fx); + return true; + } + + // Still carrying the melee weapon? + if (fx.weapon->Contained() != this) { fx.weapon=nil; return false; } + // Are we in range? + var x=GetX(), y=GetY(), tx=fx.target->GetX(), ty=fx.target->GetY(); + var dx = tx-x, dy = ty-y; + var dy_tolerance = -15; + if (fx.target->~IsMainObjective()) dy_tolerance = -40; // Don't jump in front of the windmills you will fall off the islands + + if (Abs(dx) <= 10 && PathFree(x,y,tx,ty)) + { + if (dy >= dy_tolerance) + { + // target is under us - sword slash downwards! + if (!CheckHandsAction(fx)) return true; + // Stop here + SetCommand("None"); SetComDir(COMD_None); + // cooldown? + if (!fx.weapon->CanStrikeWithWeapon(this)) + { + return true; + } + // OK, slash! + SelectItem(fx.weapon); + return fx.weapon->ControlUse(this, tx,ty); + } + if (fx.target->~IsMainObjective()) + { + // Don't jump for higher windmills, get a new target! + fx.target = nil; + SetCommand(); + return true; + } + // Clonk is above us - jump there + ExecuteJump(); + if (dx<-5) SetComDir(COMD_Left); else if (dx>5) SetComDir(COMD_Right); else SetComDir(COMD_None); + } + // Not in range. Walk there. + if (!GetCommand() || !Random(10)) SetCommand("MoveTo", fx.target); + //Message("Melee %s @ %s!!!", fx.weapon->GetName(), fx.target->GetName()); + return true; +} + +// Use shields only if still on the airship +func ExecuteProtection(fx) +{ + if (!fx.carrier) return false; + return _inherited(fx); +} + +// Don't evade you will fall off the islands +func ExecuteEvade(fx, int foo, int bar) +{ + return false; +} + +// Always shoot +private func ExecuteRanged(fx) +{ + // Still carrying the bow? + if (fx.weapon->Contained() != this) { fx.weapon=fx.post_aim_weapon=nil; return false; } + // Finish shooting process + if (fx.post_aim_weapon) + { + // wait max one second after shot (otherwise may be locked in wait animation forever if something goes wrong during shot) + if (FrameCounter() - fx.post_aim_weapon_time < 36) + if (IsAimingOrLoading()) return true; + fx.post_aim_weapon = nil; + } + // Target still in guard range? + //if (!CheckTargetInGuardRange(fx)) return false; + // Don't check for this guard range crap, fire if near enough + if (ObjectDistance(fx.target, this) > CustomAI.ARCHER_SHOOT_DIST) return false; + + // Shooting at windmill + if (fx.target->~IsMainObjective()) + { + // Look for clonks in range + if (!Random(25)) + { + var target = FindTarget(fx, true); + if (target) + fx.target = target; + } + } else { + // Firing at a clonk, maybe fire at a windmill + if (!Random(50) || !PathFree(GetX(), GetY(), fx.target->GetX(), fx.target->GetY())) + fx.target = FindTarget(fx); + if (!fx.target) return; // ??? + } + + // Look at target + ExecuteLookAtTarget(fx); + // Make sure we can shoot + if (!IsAimingOrLoading() || !fx.aim_weapon) + { + CancelAiming(fx); + if (!CheckHandsAction(fx)) return true; + // Start aiming + SelectItem(fx.weapon); + if (!fx.weapon->ControlUseStart(this, fx.target->GetX()-GetX(), fx.target->GetY()-GetY())) return false; // something's broken :( + fx.aim_weapon = fx.weapon; + fx.aim_time = fx.time; + fx.post_aim_weapon = nil; + // Enough for now + return; + } + // Stuck in aim procedure check? + if (GetEffect("IntAimCheckProcedure", this) && !this->ReadyToAction()) + return ExecuteStand(fx); + // Calculate offset to target. Take movement into account + // Also aim for the head (y-4) so it's harder to evade by jumping + var x=GetX(), y=GetY(), tx=fx.target->GetX(), ty=fx.target->GetY()-4; + var d = Distance(x,y,tx,ty); + var dt = d * 10 / fx.projectile_speed; // projected travel time of the arrow + tx += GetTargetXDir(fx.target, dt); + ty += GetTargetYDir(fx.target, dt); + if (!fx.target->GetContact(-1)) if (!fx.target->GetCategory() & C4D_StaticBack) ty += GetGravity()*dt*dt/200; + + // Get shooting angle + var shooting_angle; + if (fx.ranged_direct) + shooting_angle = Angle(x, y, tx, ty, 10); + else { + if (PathFree(x,y,tx,ty)) + shooting_angle = GetBallisticAngle(tx-x, ty-y, fx.projectile_speed, 160); + else + shooting_angle = GetUpperBallisticAngle(tx-x, ty-y, fx.projectile_speed, 160); + } + if (GetType(shooting_angle) != C4V_Nil) + { + // No ally on path? Also search for allied animals, just in case. + var ally; + if (!fx.ignore_allies) ally = FindObject(Find_OnLine(0,0,tx-x,ty-y), Find_Exclude(this), Find_OCF(OCF_Alive), Find_Owner(GetOwner())); + if (ally) + { + if (ExecuteJump()) return true; + // can't jump and ally is in the way. just wait. + } + else + { + //Message("Bow @ %d!!!", shooting_angle); + // Aim/Shoot there + x = Sin(shooting_angle, 1000, 10); + y = -Cos(shooting_angle, 1000, 10); + fx.aim_weapon->ControlUseHolding(this, x,y); + if (this->IsAiming() && fx.time >= fx.aim_time + fx.aim_wait) + { + //Log("Throw angle %v speed %v to reach %d %d", shooting_angle, fx.projectile_speed, tx-GetX(), ty-GetY()); + fx.aim_weapon->ControlUseStop(this, x,y); + fx.post_aim_weapon = fx.aim_weapon; // assign post-aim status to allow slower shoot animations to pass + fx.post_aim_weapon_time = FrameCounter(); + fx.aim_weapon = nil; + } + return true; + } + } + + // Path not free or out of range. Just wait for enemy to come... + fx.aim_weapon->ControlUseHolding(this,tx-x,ty-y); + // Might also change target if current is unreachable + var new_target; + if (!Random(3)) if (new_target = FindTarget(fx)) fx.target = new_target; + return true; +} + +// Used when no free path to target (presumably because of the airship gondola floor) +// Returns the 'upper' shooting angle, see GetBallisticAngle etc. +private func GetUpperBallisticAngle(int dx, int dy, int v, int max_angle) +{ + // v is in 1/10 pix/frame + // gravity is in 1/100 pix/frame^2 + var g = GetGravity(); + // correct vertical distance to account for integration error + // engine adds gravity after movement, so targets fly higher than they should + // thus, we aim lower. we don't know the travel time yet, so we assume some 90% of v is horizontal + // (it's ~2px correction for 200px shooting distance) + dy += Abs(dx)*g*10/(v*180); + //Log("Correction: Aiming %d lower!", Abs(dx)*q*10/(v*180)); + // q is in 1/10000 (pix/frame)^4 + var q = v**4 - g*(g*dx*dx-2*dy*v*v); // dy is negative up + if (q<0) return nil; // out of range + var a = (Angle(0, 0, g * dx, -Sqrt(q) - v * v, 10) + 1800) % 3600 - 1800; + // Check bounds + if(!Inside(a, -10 * max_angle, 10 * max_angle)) return nil; + return a; +} + +// Don't move exacty to target's position (throwing check will fail then) +private func ExecuteThrow(fx) +{ + // Still carrying the weapon to throw? + if (fx.weapon->Contained() != this) { fx.weapon=nil; return false; } + // Path to target free? + var x=GetX(), y=GetY(), tx=fx.target->GetX(), ty=fx.target->GetY(); + if (PathFree(x,y,tx,ty)) + { + var throw_speed = this.ThrowSpeed; + if (fx.weapon->GetID() == Javelin) throw_speed *= 2; + var rx = (throw_speed*throw_speed)/(100*GetGravity()); // horizontal range for 45 degree throw if enemy is on same height as we are + var ry = throw_speed*7/(GetGravity()*10); // vertical range of 45 degree throw + var dx = tx-x, dy = ty-y+15*GetCon()/100; // distance to target. Reduce vertical distance a bit because throwing exit point is not at center + // Check range + // Could calculate the optimal parabulum here, but that's actually not very reliable on moving targets + // It's usually better to throw straight at the target and only throw upwards a bit if the target stands on high ground or is far away + // Also ignoring speed added by own velocity, etc... + if (Abs(dx)*ry-Min(dy)*rx <= rx*ry) + { + // We're in range. Can throw? + if (!CheckHandsAction(fx)) return true; + // OK. Calc throwing direction + dy -= dx*dx/rx; // big math! + // And throw! + //Message("Throw!"); + SetCommand("None"); SetComDir(COMD_Stop); + SelectItem(fx.weapon); + return this->ControlThrow(fx.weapon, dx, dy); + } + } + // Can't reach target yet. Walk towards it. + if (!GetCommand() || !Random(3)) + { + var tx = fx.target->GetX(); + SetCommand("MoveTo", nil, BoundBy(GetX(), tx-30, tx+30), fx.target->GetY()); + } + //Message("Throw %s @ %s!!!", fx.weapon->GetName(), fx.target->GetName()); + return true; +} + +//====================================================================== +/* Enemy creation */ + +static g_last_airship; + +func Departure_WeaponRespawn(object container) +{ + // Weapon used? Schedule to respawn a new one! + if (container->GetAlive() || container->GetID()==Catapult) container->ScheduleCall(container, CustomAI.DoWeaponRespawn, 5, 1, GetID()); + this.Departure = nil; +} + +func DoWeaponRespawn(id_weapon) +{ + if (GetAlive() || GetID()==Catapult) + { + var re_weapon = CreateContents(id_weapon); + if (re_weapon) re_weapon.Departure = CustomAI.Departure_WeaponRespawn; + return re_weapon; + } +} + +func Inventory_GetCarryTransform() +{ + if (GetID().GetCarryTransform) + return Trans_Mul(Call(GetID().GetCarryTransform, ...), this.ExtraTransform); + else + return this.ExtraTransform; +} + +func LaunchEnemy(proplist enemy, int xmin, int xrange, int ymin, yrange) +{ + // Create enemy (usually a Clonk) + var x = xmin+Random(xrange); + var y = ymin+Random(yrange); + var obj = CreateObjectAbove(enemy.Type ?? Clonk, x,y, ENEMY), clonk; + if (!obj) return nil; + obj->SetController(ENEMY); + // Enemy visuals + if (enemy.Skin) + { + if (GetType(enemy.Skin) == C4V_Array) + { + obj->SetSkin(enemy.Skin[0]); + obj->SetMeshMaterial(enemy.Skin[1]); + } + else + obj->SetSkin(enemy.Skin); + } + if (GetType(enemy.Backpack)) obj->~RemoveBackpack(); + if (enemy.Scale) obj->SetMeshTransformation(Trans_Scale(enemy.Scale[0], enemy.Scale[1], enemy.Scale[2]), 6); + if (enemy.Name) obj->SetName(enemy.Name); + obj->SetColor(enemy.Color); + // Physical properties + obj.MaxEnergy = (enemy.Energy ?? 50) * 1000; + obj->DoEnergy(10000); + if (enemy.Speed) + { + // Speed: Modify Speed in all ActMap entries + if (obj.ActMap == obj.Prototype.ActMap) obj.ActMap = new obj.ActMap {}; + for (var action in /*obj.ActMap->GetProperties()*/ ["Walk", "Scale", "Dig", "Swim", "Hangle", "Jump", "WallJump", "Dive", "Push"]) // obj.ActMap->GetProperties() doesn't work :( + { + if (action == "Prototype") continue; + if (obj.ActMap[action] == obj.Prototype.ActMap[action]) obj.ActMap[action] = new obj.ActMap[action] {}; + obj.ActMap[action].Speed = obj.ActMap[action].Speed * enemy.Speed / 100; + } + obj.JumpSpeed = obj.JumpSpeed * enemy.Speed / 100; + obj.FlySpeed = obj.FlySpeed * enemy.Speed / 100; + } + obj.MaxContentsCount = CustomAI.Clonk_MaxContentsCount; + obj->MakeInvincibleToFriendlyFire(); + obj.MaxContentsCountVal = 2; + // Reward for killing enemy + obj.Bounty = enemy.Bounty; + // Vehicles + var vehicle; + if (enemy.Vehicle) + { + if (enemy.Vehicle == Balloon) + { + Balloon->ControlUseStart(obj); + } else if (enemy.Vehicle == Boomattack) { + vehicle = CreateObjectAbove(enemy.Vehicle, x,y+10, ENEMY); + // Add boomattack to enemy array + g_spawned_enemies[GetLength(g_spawned_enemies)] = vehicle; + obj->SetAction("Ride", vehicle); + } else { + vehicle = CreateObjectAbove(enemy.Vehicle, x,y+10, ENEMY); + obj->SetAction("Push", vehicle); + if (vehicle && vehicle->GetID() == Airship) + g_last_airship = vehicle; + } + } + // Enemy inventory + if (enemy.Inventory) for (var inv in ForceVal2Array(enemy.Inventory)) + { + var inv_type = inv.InvType; + if (!inv_type) inv_type = inv; + var inv_obj = obj->CreateContents(inv_type); + if (inv_obj) + { + // Marker for custom weapon behaviour + inv_obj.IsAIWeapon = true; + // Infinite ammo + inv_obj->~SetInfiniteStackCount(); + if (GetIndexOf(g_respawning_weapons, inv_type) >= 0) inv_obj.Departure = CustomAI.Departure_WeaponRespawn; + // Extra settings? + if (inv.InvType) + { + // Visuals + if (inv.Scale) { inv_obj.GetCarryTransform = CustomAI.Inventory_GetCarryTransform; inv_obj.ExtraTransform = Trans_Scale(inv.Scale, inv.Scale, inv.Scale); } + if (inv.Material) inv_obj->SetMeshMaterial(inv.Material); + // Strength + if (inv.Strength) inv_obj->SetStrength(inv.Strength); + } + } + } + // Flying AI + if (obj->~IsFlyingEnemy()) + { + // Flying enemies all init themselves to fly at the statue at the moment + } + else + { + // Init AI: Run towards statue + CustomAI->AddAI(obj); + CustomAI->SetMaxAggroDistance(obj, LandscapeWidth()); + CustomAI->SetGuardRange(obj, 0,0,LandscapeWidth(),LandscapeHeight()); // nowhere to run! + var fx = GetEffect("AI", obj); + if (fx) fx.vehicle = vehicle; + if (enemy.IsCrew) // is airship crew member, spawn on last created airship + if (g_last_airship) + { + obj->SetPosition(g_last_airship->GetX() + RandomX(-15,15), g_last_airship->GetY()+12); + if (fx) + fx.carrier = g_last_airship; + } + } + // Remember this clonk to end wave when all enemies have been killed + g_spawned_enemies[GetLength(g_spawned_enemies)] = obj; + return obj; +} + +// forward max contents count to property +func Clonk_MaxContentsCount() { return this.MaxContentsCountVal; } \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/DescDE.rtf b/planet/Defense.ocf/Windmill.ocs/DescDE.rtf index 398d0ac94..306970105 100644 Binary files a/planet/Defense.ocf/Windmill.ocs/DescDE.rtf and b/planet/Defense.ocf/Windmill.ocs/DescDE.rtf differ diff --git a/planet/Defense.ocf/Windmill.ocs/DescUS.rtf b/planet/Defense.ocf/Windmill.ocs/DescUS.rtf index 4eb318351..64af6b8b4 100644 Binary files a/planet/Defense.ocf/Windmill.ocs/DescUS.rtf and b/planet/Defense.ocf/Windmill.ocs/DescUS.rtf differ diff --git a/planet/Defense.ocf/Windmill.ocs/Map.bmp b/planet/Defense.ocf/Windmill.ocs/MapBg.bmp similarity index 92% rename from planet/Defense.ocf/Windmill.ocs/Map.bmp rename to planet/Defense.ocf/Windmill.ocs/MapBg.bmp index 9ff2d4084..902e3ab2c 100644 Binary files a/planet/Defense.ocf/Windmill.ocs/Map.bmp and b/planet/Defense.ocf/Windmill.ocs/MapBg.bmp differ diff --git a/planet/Defense.ocf/Windmill.ocs/MapFg.bmp b/planet/Defense.ocf/Windmill.ocs/MapFg.bmp new file mode 100644 index 000000000..d5dedba9b Binary files /dev/null and b/planet/Defense.ocf/Windmill.ocs/MapFg.bmp differ diff --git a/planet/Defense.ocf/Windmill.ocs/NextWave.ogg b/planet/Defense.ocf/Windmill.ocs/NextWave.ogg new file mode 100644 index 000000000..b4e3fcb23 Binary files /dev/null and b/planet/Defense.ocf/Windmill.ocs/NextWave.ogg differ diff --git a/planet/Defense.ocf/Windmill.ocs/Objects.c b/planet/Defense.ocf/Windmill.ocs/Objects.c new file mode 100644 index 000000000..afc63727e --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/Objects.c @@ -0,0 +1,56 @@ +/* Automatically created objects file */ + +static g_goal, g_object_fade, g_flag, g_windgen1, g_windgen2, g_windgen3, g_windmill, g_chest, g_windbag, g_lorry; + +func InitializeObjects() +{ + g_goal = CreateObject(Goal_SaveTheWindmills, 10, 10); + g_goal.StaticSaveVar = "g_goal"; + + g_object_fade = CreateObject(Rule_ObjectFade, 0, 0); + g_object_fade.StaticSaveVar = "g_object_fade"; + + g_flag = CreateObject(Flagpole, 1033, 937); + g_flag.StaticSaveVar = "g_flag"; + g_flag->SetNeutral(true); + + g_windgen1 = CreateObject(WindGenerator, 1041, 799); + g_windgen1.StaticSaveVar = "g_windgen1"; + g_windgen2 = CreateObject(WindGenerator, 1105, 907); + g_windgen2.StaticSaveVar = "g_windgen2"; + g_windgen3 = CreateObject(WindGenerator, 898, 908); + g_windgen3.StaticSaveVar = "g_windgen3"; + g_windmill = CreateObjectAbove(Windmill, 991, 970); + g_windmill.StaticSaveVar = "g_windmill"; + + g_chest = CreateObjectAbove(Chest, 997, 1023); + g_chest.StaticSaveVar = "g_chest"; + g_windbag = g_chest->CreateContents(WindBag); + g_windbag.StaticSaveVar = "g_windbag"; + + g_lorry = CreateObjectAbove(Lorry, 924, 938); + g_lorry.StaticSaveVar = "g_lorry"; + + CreateObjectAbove(Grass, 1006, 826); + CreateObjectAbove(Grass, 990, 826); + CreateObjectAbove(Grass, 955, 835); + CreateObjectAbove(Grass, 975, 827); + + CreateObjectAbove(Flower, 965, 840); + CreateObjectAbove(Flower, 1016, 835); + + var Trunk001 = CreateObject(Trunk, 827, 937); + Trunk001->SetR(-30); + + CreateObjectAbove(Tree_Coniferous3, 1167, 945); + + var Branch001 = CreateObject(Branch, 938, 839); + Branch001->SetR(-30); + + var Ropeladder001 = CreateObject(Ropeladder, 1057, 826); + Ropeladder001->Unroll(DIR_Right); + + CreateObject(Basement, 957, 859); + + return true; +} diff --git a/planet/Defense.ocf/Windmill.ocs/ParticlePlanks.ocd/Graphics.png b/planet/Defense.ocf/Windmill.ocs/ParticlePlanks.ocd/Graphics.png new file mode 100644 index 000000000..927e439a6 Binary files /dev/null and b/planet/Defense.ocf/Windmill.ocs/ParticlePlanks.ocd/Graphics.png differ diff --git a/planet/Defense.ocf/Windmill.ocs/ParticlePlanks.ocd/Particle.txt b/planet/Defense.ocf/Windmill.ocs/ParticlePlanks.ocd/Particle.txt new file mode 100644 index 000000000..600fc97ae --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/ParticlePlanks.ocd/Particle.txt @@ -0,0 +1,3 @@ +[Particle] +Name=Planks +Face=0,0,12,104,-6,-52 diff --git a/planet/Defense.ocf/Windmill.ocs/SavePlayerInfos.txt b/planet/Defense.ocf/Windmill.ocs/SavePlayerInfos.txt new file mode 100644 index 000000000..538c2602e --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/SavePlayerInfos.txt @@ -0,0 +1,15 @@ +[PlayerInfoList] +LastPlayerID=1 + + [Client] + ID=0 + Flags=Initial + + [Player] + Name="Don Quijote" + Type=Script + Flags=Joined|AttributesFixed|NoScenarioInit|NoEliminationCheck + ID=1 + Color=4294901760 + Team=2 + GameNumber=10 diff --git a/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/Script.c b/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/Script.c index cb2e4c545..05ce0342a 100644 --- a/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/Script.c +++ b/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/Script.c @@ -2,111 +2,17 @@ #include Library_Goal -static const SBRD_Rockets = 1; -local score, boss, wave; - -func Initialize() -{ - score = []; - boss = false; - wave = 1; - // init scoreboard - Scoreboard->Init( - [{key = "windmills", title = Goal_SaveTheWindmills, sorted = true, desc = true, default = 0, priority = 80}] - ); - Scoreboard->SetTitle("$ScoreCaption$"); - // Remove settlement eval data. - HideSettlementScoreInEvaluation(true); - inherited(...); -} - public func IsFulfilled() { - if(!FindObject(Find_ID(WindGenerator))) - { - for(var i = 0; iSetPlayerData(plr, "windmills", score[plrid]); - } - NotifyHUD(); -} - -public func SetWave(int num) -{ - wave = num; -} - -public func BossAttacks() -{ - boss = true; -} - -public func GetShortDescription(int plr) -{ - //var allscore = 0; - //for(var i=0; iNewPlayerEntry(plr); - return _inherited(plr, ...); -} - -protected func RemovePlayer(int plr) -{ - var plrid = GetPlayerID(plr); - AddEvaluationData(Format("$MsgEval$", score[plrid]), plrid); - return _inherited(plr, ...); -} - -public func OnGameOver() -{ - for (var i = 0; i < GetPlayerCount(); i++) - { - var plrid = GetPlayerID(GetPlayerByIndex(i)); - AddEvaluationData(Format("$MsgEval$", score[plrid]), plrid); - } - return true; -} - local Name = "$Name$"; local Description = "$Description$"; diff --git a/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/StringTblDE.txt b/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/StringTblDE.txt index ecd4263e3..5dd5cea2a 100644 --- a/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/StringTblDE.txt +++ b/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/StringTblDE.txt @@ -1,8 +1,2 @@ -Name=Rette die Windräder -ShotScore=%d abgeschossen -Shots=Treffer -ScoreCaption=Die beste Hüter -WaveCount=Wave %d -WaveBoss=Boss wave -MsgEval=You have shot %d windmill-eating rockets. -Description=Zerstöre die Windrad-fressenden Raketen bevor sie die Windräder erreichen! Wenn kein Windrad mehr übrig ist, verlierst Du \ No newline at end of file +Name=Hüte die Windräder +Description=Verteidige die Windräder gegen die Feinde! \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/StringTblUS.txt b/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/StringTblUS.txt index a689c6a41..45c9e84ec 100644 --- a/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/StringTblUS.txt +++ b/planet/Defense.ocf/Windmill.ocs/SaveTheWindmills.ocd/StringTblUS.txt @@ -1,8 +1,2 @@ -Name=Save the windmills -ShotScore=shot %d -Shots=Shot -ScoreCaption=The best guardian -WaveCount=Wave %d -WaveBoss=Boss wave -MsgEval=You have shot %d windmill-eating rockets. -Description=Destroy the windmill-eating rockets before they reach the windmills! If no windmill remains, you loose. \ No newline at end of file +Name=Guard the windmills +Description=Defend the windmills against the enemies! \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/Scenario.txt b/planet/Defense.ocf/Windmill.ocs/Scenario.txt index 08bc93202..3a5872885 100644 --- a/planet/Defense.ocf/Windmill.ocs/Scenario.txt +++ b/planet/Defense.ocf/Windmill.ocs/Scenario.txt @@ -1,29 +1,28 @@ [Head] Icon=39 Title=Windmill -Version=6,0 +Version=7 Difficulty=90 [Definitions] -Definition1=Objects.ocd +Definition2=Decoration.ocd [Player1] -Crew=Clonk=1 +Wealth=50,0,0,250 [Player2] -Crew=Clonk=1 +Wealth=50,0,0,250 [Player3] -Crew=Clonk=1 +Wealth=50,0,0,250 [Player4] -Crew=Clonk=1 +Wealth=50,0,0,250 [Landscape] Sky=Sky MapZoom=10 BottomOpen=1 -SkyScrollMode=2 [Weather] Climate=0,0,0,100 diff --git a/planet/Defense.ocf/Windmill.ocs/Script.c b/planet/Defense.ocf/Windmill.ocs/Script.c index 4a1adf6df..2b301a7a9 100644 --- a/planet/Defense.ocf/Windmill.ocs/Script.c +++ b/planet/Defense.ocf/Windmill.ocs/Script.c @@ -1,134 +1,511 @@ /* - Boom command - Authors: Randrian, Newton - - Funny little survival scenario to train with the bow. + Guardians of the Windmills + Authors: Randrian, Newton, Clonkonaut + + Defend the windmills against waves of enemies */ -// in seconds -static const Boomattack_wave_delay = 25; +static g_goal, g_object_fade, g_flag, g_windgen1, g_windgen2, g_windgen3, g_windmill, g_chest, g_windbag, g_lorry; -static const Boomattack_angle_spread = 45; -// the bigger this value, the slower does the attack size grow -static const Boomattack_attack_growth = 90; +static g_wave; // index of current wave +static g_spawned_enemies; +static g_relaunchs; // array of relaunch counts +static g_scores; // array of player scores +static g_ai; // derived from AI; contains changes for this scenario +static g_homebases; // item management / buy menus for each player +static g_lost; // True if all windmills are destroyed +static const ENEMY = 10; // player number of enemy +static const ALLOW_DEBUG_COMMANDS = true; + +static const MAX_RELAUNCH = 10; + +static shared_wealth_remainder; + +//====================================================================== +/* Initialization */ func Initialize() { - var offs = 45; - CreateObjectAbove(WindGenerator, 1147, 938+offs)->SetR(7); - CreateObjectAbove(WindGenerator, 985, 1105+offs)->SetR(-170); - CreateObjectAbove(WindGenerator, 1055, 1085+offs)->SetR(140); - CreateObjectAbove(WindGenerator, 971, 857+offs)->SetR(-20); - CreateObjectAbove(WindGenerator, 1147, 1035+offs)->SetR(160); - CreateObjectAbove(WindGenerator, 1036, 870+offs)->SetR(-10); - CreateObjectAbove(WindGenerator, 1081, 873+offs)->SetR(18); - CreateObjectAbove(WindGenerator, 858, 930+offs)->SetR(-10); - - CreateObject(Goal_SaveTheWindmills,10,10); - PlaceGrass(100, 800, 1400); - SetSkyParallax(0,25,25,0,0,0,0); - AddEffect("BoomAttack", nil, 100, 35); - Sound("Environment::WindLoop",true,40,nil,+1); + // dev stuff (we will forget to turn this off for release) + AddMsgBoardCmd("waveinfo", "GameCall(\"ShowWaveInfo\")"); + AddMsgBoardCmd("next", "GameCall(\"SetNextWave\", \"%s\")"); + AddMsgBoardCmd("nextwait", "GameCall(\"SetNextWave\", \"%s\", true)"); + AddMsgBoardCmd("scrooge", "GameCall(\"DoWealthForAll\", 1000000000)"); + // Wealth shown at all time + GUI_Controller->ShowWealth(); + // static variable init + g_homebases = []; + InitWaveData(); } -global func FxBoomAttackTimer(object target, effect, int time) +func InitializePlayer(int plr, int iX, int iY, object pBase, int iTeam) { - var wave = 1+time/35/Boomattack_wave_delay; + if (GetPlayerType(plr) != C4PT_User) return; - if(time/35 % Boomattack_wave_delay == 1) + if (g_lost) { EliminatePlayer(plr); return; } // no post-elimination join + if (!g_relaunchs) { - var gol = FindObject(Find_ID(Goal_SaveTheWindmills)); - if(gol) - gol->SetWave(wave); - if (wave < 13) - { - CustomMessage(Format(" $MsgWave$ ",wave),nil,NO_OWNER); - var wave_strength = Sqrt(9+GetPlayerCount()*time/Boomattack_attack_growth); - CreateAttackWave( Random(360) , wave_strength,Boomattack_angle_spread); - } - else if (wave == 13) - { - CustomMessage(" $MsgBoss$ ",nil,NO_OWNER); - CreateAttackWave( Random(360) , -1, Boomattack_angle_spread); - var gol = FindObject(Find_ID(Goal_SaveTheWindmills)); - if(gol) gol->BossAttacks(); - } - + g_relaunchs = []; + g_scores = []; + Scoreboard->Init([{key = "relaunchs", title = Rule_Restart, sorted = true, desc = true, default = "", priority = 75}, + {key = "score", title = Nugget, sorted = true, desc = true, default = "0", priority = 100}]); } -} + g_relaunchs[plr] = MAX_RELAUNCH; + g_scores[plr] = 0; + Scoreboard->NewPlayerEntry(plr); + Scoreboard->SetPlayerData(plr, "relaunchs", g_relaunchs[plr]); + Scoreboard->SetPlayerData(plr, "score", g_scores[plr]); -global func CreateAttackWave(int angle, int rockets, int anglespread) -{ - var radius = Min(LandscapeWidth()/2, LandscapeHeight()/2); - var rocket_id = Boomattack; - // boss - if(rockets == -1) - { - rockets = 1; - rocket_id = BigBoomattack; - } - - for(var i=0; iLaunch(rocket_angle + 180); - } - - for(var i=0; iSetAction("Show", GetCursor(owner)); - gui_arrow->SetClrModulation(RGB(255,0,0)); - gui_arrow->SetObjectBlitMode(GFX_BLIT_Additive); - } - gui_arrow->SetR(angle); - gui_arrow.Plane = 500; - } -} + SetPlayerZoomByViewRange(plr, 1200, 0, PLRZOOM_LimitMax); -func InitializePlayer(int iPlr, int iX, int iY, object pBase, int iTeam) -{ - SetFoW(false,iPlr); - JoinPlayer(iPlr); - GetHiRank(iPlr)->SetPosition(LandscapeWidth()/2, LandscapeHeight()/2); - return; -} + //DoWealth(plr, 10000000); -func RemovePlayer(int iPlr) -{ - for(var obj in FindObjects(Find_Owner(iPlr))) - { - if(obj) - obj->RemoveObject(); - } -} - -func RelaunchPlayer(int plr) -{ - var clonk = CreateObjectAbove(Clonk, LandscapeWidth()/2, 600, plr); - clonk->MakeCrewMember(plr); - SetCursor(plr, clonk); JoinPlayer(plr); - var gui_arrow = FindObject(Find_ID(GUI_GoalArrow), Find_Owner(plr)); - gui_arrow->SetAction("Show", GetCursor(plr)); + if (!g_wave) StartGame(); } -func JoinPlayer(int iPlr) +func RemovePlayer(int plr) { - var clonk = GetCrew(iPlr); - clonk->DoEnergy(1000); + Scoreboard->SetPlayerData(plr, "relaunchs", Icon_Cancel); + // Split player's wealth among the remaining players + ScheduleCall(nil, Scenario.DoSharedWealth, 50, 1, GetWealth(plr)); +} - clonk->CreateContents(Bow); - clonk->Collect(CreateObjectAbove(Arrow)); - clonk->CreateContents(Musket); - clonk->Collect(CreateObjectAbove(LeadShot)); +private func TransferInventory(object from, object to) +{ + // Drop some items that cannot be transferred (such as connected pipes and dynamite igniters) + var i = from->ContentsCount(), contents; + while (i--) + if (contents = from->Contents(i)) + if (contents->~IsDroppedOnDeath(from)) + contents->Exit(); + return to->GrabContents(from); +} + +func JoinPlayer(plr, prev_clonk) +{ + var x=991,y = 970; + var clonk = GetCrew(plr); + if (clonk) + { + clonk->SetPosition(x,y-10); + } + else + { + clonk = CreateObjectAbove(Clonk, x,y, plr); + clonk->MakeCrewMember(plr); + } + SetCursor(plr, clonk); + clonk->DoEnergy(1000); + clonk->MakeInvincibleToFriendlyFire(); + // contents + clonk.MaxContentsCount = CustomAI.Clonk_MaxContentsCount; + clonk.MaxContentsCountVal = 1; + if (prev_clonk) TransferInventory(prev_clonk, clonk); + if (!clonk->ContentsCount()) + { + clonk->CreateContents(Bow); + var arrow = CreateObjectAbove(Arrow); + clonk->Collect(arrow); + arrow->SetInfiniteStackCount(); + } + clonk->~CrewSelection(); // force update HUD +} + +// Enter all buyable things into the homebase +func FillHomebase(object homebase) +{ + // Quick buy items on hotkeys + homebase->SetQuickbuyItems([WindBag, Bow, Javelin, Musket, GrenadeLauncher, nil, nil, nil, nil, nil]); + + // Buy menu entries + homebase->AddCaption("$HomebaseWeapons$"); + homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = Bow, ammo = Arrow, desc = "$HomebaseDescBow$" }); + homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = Javelin, cost = 1, desc = "$HomebaseDescJavelin$" , infinite = true}); + homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = Musket, cost = 50, ammo = LeadShot, desc = "$HomebaseDescMusket$", requirements = ["AdvancedWeapons"] }); + homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = GrenadeLauncher, ammo = IronBomb, desc = "$HomebaseDescGrenadeLauncher$", requirements = ["MasterWeapons"] }); + homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = WindBag, cost = 500, desc = "$HomebaseDescWindBag$", requirements = ["MasterWeapons"] }); + + homebase->AddCaption("$HomebaseItems$"); + homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Consumable { item = Bread, cost = 5 }); +// homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Weapon { item = Hammer, cost = 1000, desc = "$HomebaseDescHammer$", extra_width = 1 }); + + homebase->AddCaption("$HomebaseTechnology$"); + homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Technology { name="$HomebaseAdvancedWeapons$", item = Icon_World,cost = 100, desc="$HomebaseDescAdvancedWeapons$", tech = "AdvancedWeapons" }); + homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Technology { name="$HomebaseMasterWeapons$", item = Icon_World,cost = 1000, desc = "$HomebaseDescMasterWeapons$", tech = "MasterWeapons", requirements = ["AdvancedWeapons"] }); + + homebase->AddCaption("$HomebaseUpgrades$"); + homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Technology { name="$HomebaseLoadSpeed$", item = Homebase_Icon, graphics="LoadSpeed%d", costs = [100, 500, 1000], desc = "$HomebaseDescLoadSpeed$", tech = "LoadSpeed", tiers=3 }); + homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Technology { name="$HomebaseShootingStrength$", item = Homebase_Icon, graphics="ShootingStrength%d", costs = [50, 150, 350], desc = "$HomebaseDescShootingStrength$", tech = "ShootingStrength", tiers=3 }); + homebase->AddHomebaseItem(new Homebase.ITEMTYPE_Technology { name="$HomebaseLife$", item = Homebase_Icon, graphics="Life%d", costs = [10, 50, 100], desc = "$HomebaseDescLife$", tech = "Life", tiers=3 }); + homebase->AddCaption("$HomebaseArtifacts$"); +} + +// Clonk death callback +func OnClonkDeath(clonk, killed_by) +{ + // Player died? + if (!clonk) return; + var plr = clonk->GetOwner(); + if (GetPlayerType(plr) == C4PT_User) + { + // Relaunch count + if (!g_relaunchs[plr]) + { + Log("$MsgOutOfRelaunchs$", GetTaggedPlayerName(plr)); + Scoreboard->SetPlayerData(plr, "relaunchs", Icon_Cancel); + EliminatePlayer(plr); + return false; + } + // Relaunch count + --g_relaunchs[plr]; + Scoreboard->SetPlayerData(plr, "relaunchs", g_relaunchs[plr]); + Log("$MsgRelaunch$", GetTaggedPlayerName(plr)); + JoinPlayer(plr, clonk); + } + else + { + // Enemy clonk death + // Remove inventory + var i = clonk->ContentsCount(), obj; + while (i--) if (obj=clonk->Contents(i)) + if (!obj->~OnContainerDeath()) + obj->RemoveObject(); + // Clear enemies from list + i = GetIndexOf(g_spawned_enemies, clonk); + if (i>=0) + { + g_spawned_enemies[i] = nil; + // Kill bounty + if (killed_by>=0) + { + Scoreboard->SetPlayerData(killed_by, "score", ++g_scores[killed_by]); + DoWealth(killed_by, clonk.Bounty); + } + else + { + // Killer could not be determined. Just give gold to everyone. + DoSharedWealth(clonk.Bounty); + } + } + } return; } + +//====================================================================== +/* The Game */ + +func StartGame() +{ + // Init objects to defend + var obj; + for (obj in [g_windgen1, g_windgen2, g_windgen3, g_windmill]) if (obj) + { + obj->SetCategory(C4D_Living); + obj->SetAlive(true); + obj.MaxEnergy = 800000; + obj->DoEnergy(obj.MaxEnergy/1000); + obj->AddEnergyBar(); + obj.FxNoPlayerDamageDamage = Scenario.Object_NoPlayerDamage; + AddEffect("NoPlayerDamage", obj, 500, 0, obj); + } + // Launch first wave! + g_wave = 1; + ScheduleCall(nil, Scenario.LaunchWave, 50, 1, g_wave); + return true; +} + +func Object_NoPlayerDamage(object target, fx, dmg, cause, cause_player) +{ + // players can't damage windmills + if (GetPlayerType(cause_player) == C4PT_User) return 0; + return dmg; +} + +public func WindmillDown(object windmill) +{ + if (g_windgen1 == windmill) g_windgen1 = nil; + if (g_windgen2 == windmill) g_windgen2 = nil; + if (g_windgen3 == windmill) g_windgen3 = nil; + if (g_windmill == windmill) g_windmill = nil; + + Sound("Objects::Plane::PlaneCrash", true); + + // Nothing left to defend? + if (!g_windgen1 && !g_windgen2 && !g_windgen3 && !g_windmill) + { + // Fail! + var i=GetPlayerCount(C4PT_User); + while (i--) EliminatePlayer(GetPlayerByIndex(i, C4PT_User)); + g_lost = true; + ScheduleCall(nil, Global.GameOver, 50, 1); + } +} + +public func DoSharedWealth(int amount) +{ + // Split gold among all players. Keep track of remainder and use it next time + shared_wealth_remainder += amount; + var cnt = GetPlayerCount(C4PT_User); + if (cnt) + { + var wealth_add = shared_wealth_remainder / cnt; + if (wealth_add) + { + shared_wealth_remainder -= wealth_add * cnt; + DoWealthForAll(wealth_add); + } + } + return true; +} + +public func DoWealthForAll(int amount) +{ + // Add wealth to all players + for (var iplr = 0; iplr < GetPlayerCount(C4PT_User); ++iplr) + DoWealth(GetPlayerByIndex(iplr, C4PT_User), amount); + return true; +} + +//====================================================================== +/* Enemy waves */ + +func LaunchWave(int wave) +{ + // * Schedules spawning of all enemies + // * Schedules call to LaunchWaveDone() after last enemy has been spawned + var wave_data = ENEMY_WAVE_DATA[g_wave]; + g_spawned_enemies = []; + if (wave_data) + { + var wave_spawn_time = 0; + CustomMessage(Format("$MsgWave$: %s", wave, wave_data.Name)); + Sound("UI::Ding"); + for (var enemy in ForceVal2Array(wave_data.Enemies)) if (enemy) + { + if (enemy.Delay) + ScheduleCall(nil, Scenario.ScheduleLaunchEnemy, enemy.Delay, 1, enemy); + else + ScheduleLaunchEnemy(enemy); + wave_spawn_time = Max(wave_spawn_time, enemy.Delay + enemy.Interval * enemy.Num); + } + for (var arrow in ForceVal2Array(wave_data.Arrows)) + { + CreateArrowForPlayers(arrow.X, arrow.Y); + } + ScheduleCall(nil, Scenario.LaunchWaveDone, wave_spawn_time+5, 1, wave); + return true; + } + return false; +} + +func CreateArrowForPlayers(int x, int y) +{ + for (var i = 0; i < GetPlayerCount(C4PT_User); i++) + { + var plr = GetPlayerByIndex(i, C4PT_User); + var cursor = GetCursor(plr); + if (!cursor) continue; + var arrow = CreateObject(GUI_GoalArrow, cursor->GetX(), cursor->GetY(), plr); + if (!arrow) continue; + arrow->SetAction("Show", cursor); + arrow->SetR(Angle(cursor->GetX(), cursor->GetY(), x, y)); + arrow->SetClrModulation(RGBa(255, 50, 0, 128)); + Schedule(arrow, "RemoveObject()", 100); + } +} + +func ScheduleLaunchEnemy(proplist enemy) +{ + // Schedules spawning of enemy definition + // Spawn on ground or in air? + var xmin, xmax, ymin, ymax; + var def = enemy.Type ?? enemy.Vehicle; + if (!def) def = Clonk; + var width = def->GetDefWidth(); + var height = def->GetDefWidth(); + + xmin = BoundBy(enemy.PosX - 100, 0 + width/2, LandscapeWidth() - width/2); + xmax = BoundBy(enemy.PosX + 100, 0 + width/2, LandscapeWidth() - width/2); + ymin = BoundBy(enemy.PosY - 100, 0 + height/2, LandscapeHeight() - height/2); + ymax = BoundBy(enemy.PosY + 100, 0 + height/2, LandscapeHeight() - height/2); + + ScheduleCall(nil, CustomAI.LaunchEnemy, Max(enemy.Interval,1), Max(enemy.Num,1), enemy, xmin, xmax - xmin, ymin, ymax - ymin); + return true; +} + +func LaunchWaveDone(int wave) +{ + // All enemies spawned! Now start timer to check whether they are all dead + ScheduleCall(nil, Scenario.CheckWaveCleared, 20, 9999999, wave); + return true; +} + +func CheckWaveCleared(int wave) +{ + // Check timer to determine if enemy wave has been cleared. + // Enemies nil themselves when they're dead. So clear out nils and we're done when the list is empty + var nil_idx; + while ( (nil_idx=GetIndexOf(g_spawned_enemies))>=0 ) + { + var l = GetLength(g_spawned_enemies) - 1; + if (nil_idx+%d{{Icon_Wealth}}", bounty); + DoWealthForAll(bounty); + } + CustomMessage(Format("$MsgWaveCleared$%s| ", wave, bounty_msg)); + Sound("NextWave"); + // Fade out stuff + Airship->AllStop(); + if (g_object_fade) + for (var obj in FindObjects(Find_Or(Find_And(Find_ID(Clonk), Find_Not(Find_OCF(OCF_Alive))), Find_ID(Catapult), Find_ID(Airship)))) + obj->AddEffect("IntFadeOut", obj, 100, 1, g_object_fade, Rule_ObjectFade); + // Next wave! + ++g_wave; + if (ENEMY_WAVE_DATA[g_wave]) + ScheduleCall(nil, Scenario.LaunchWave, 500, 1, g_wave); + else + { + // There is no next wave? Game done D: + ScheduleCall(nil, Scenario.OnAllWavesCleared, 50, 1); + } +} + +//====================================================================== +/* Game end */ + +func OnAllWavesCleared() +{ + // Success! + if (g_goal) g_goal.is_fulfilled = true; + if (GetPlayerType(ENEMY) == C4PT_Script) EliminatePlayer(ENEMY); + GainScenarioAchievement("Done"); + GameOver(); + return true; +} + +//====================================================================== +/* Wave and enemy definitions */ + +static const CSKIN_Default = 0, + CSKIN_Steampunk = 1, + CSKIN_Alchemist = 2, + CSKIN_Farmer = 3; + +static ENEMY_WAVE_DATA; + +static const g_respawning_weapons = [Firestone, Rock]; + +func InitWaveData() +{ + // Define different enemy types + var pilot = { Name="$EnemyPilot$", Inventory=Rock, Energy= 30, Bounty= 10, Color=0xff0000ff, Skin=CSKIN_Alchemist, Backpack=0, Vehicle=Airship }; + var swordman = { Name="$EnemyCrewman$", Inventory=Sword, Energy= 50, Bounty= 15, Color=0xffff0000, Skin=CSKIN_Default, Backpack=0, IsCrew=true }; + var defender = { Name="$EnemyDefender$", Inventory=[Shield, Axe],Energy= 50, Bounty= 5, Color=0xff00ff00, Skin=CSKIN_Farmer, Backpack=0, IsCrew=true }; + var bowman = { Name="$EnemyBow$", Inventory=[Bow, Arrow], Energy= 30, Bounty= 10, Color=0xff80ff80, Skin=CSKIN_Steampunk, Backpack=0, IsCrew=true }; + var ballooner = { Name="$EnemyBalloon$", Inventory=Sword, Energy= 30, Bounty= 15, Color=0xff008000, Skin=CSKIN_Default, Vehicle=Balloon }; + var rocketeer = { Name="$EnemyRocket$", Inventory=[Bow, Arrow], Energy= 15, Bounty= 10, Color=0xffffffff, Skin=CSKIN_Steampunk, Vehicle=Boomattack }; + var boomattack = { Type=Boomattack, Bounty=1 }; + var boomattackf= { Type=Boomattack, Bounty=15, Speed=300 }; + + // Define composition of waves + ENEMY_WAVE_DATA = [nil, + { Name = "$WaveFirst$", Bounty = 1, Enemies = + new boomattack { Num= 1, Interval=10, PosX = 0, PosY = 500 }, + Arrows = { X = 0, Y = 500 } + }, { Name = "$WaveSecond$", Bounty = 30, Enemies = + [new boomattack { Num= 3, Interval=10, PosX = 0, PosY = 500 }, + new boomattack { Num= 3, Interval=20, PosX = 2000, PosY = 500 },], + Arrows = [{ X = 0, Y = 500 },{ X = 2000, Y = 500 }] + }, { Name = "$WaveThird$", Bounty = 10, Enemies = + new rocketeer { Num= 8, PosX = 0, PosY = 500 }, + Arrows = { X = 0, Y = 500 } + }, { Name = "$WaveFourth$", Bounty = 15, Enemies = + [new rocketeer { Num= 8, PosX = 0, PosY = 500 }, + new rocketeer { Num= 8, PosX = 2000, PosY = 500 },], + Arrows = [{ X = 0, Y = 500 },{ X = 2000, Y = 500 }] + }, { Name = "$WaveFifth$", Bounty = 20, Enemies = + [new boomattack { Num= 10, PosX = 1000, PosY = 2000 }, + new pilot { Num= 1, Interval = 1, PosX = 2000, PosY = 750 }, + new defender { Num= 1, Interval = 2, PosX = 2000, PosY = 750 }, + new pilot { Num= 1, Interval = 3, PosX = 0, PosY = 750 }, + new defender { Num= 1, Interval = 4, PosX = 0, PosY = 750 },], + Arrows = [{ X = 0, Y = 750 },{ X = 2000, Y = 750 },{ X = 1000, Y = 2000 }] + }, { Name = "$WaveSixth$", Bounty = 20, Enemies = + [new pilot { Num= 1, Interval = 1, PosX = 2000, PosY = 1250 }, + new defender { Num= 2, Interval = 2, PosX = 2000, PosY = 1250 }, + new bowman { Num= 2, Interval = 2, PosX = 2000, PosY = 1250 }, + new swordman { Num= 1, Interval = 2, PosX = 2000, PosY = 1250 }, + new pilot { Num= 1, Interval = 3, PosX = 0, PosY = 1250 }, + new defender { Num= 2, Interval = 4, PosX = 0, PosY = 1250 }, + new bowman { Num= 2, Interval = 4, PosX = 0, PosY = 1250 }, + new swordman { Num= 1, Interval = 4, PosX = 0, PosY = 1250 },], + Arrows = [{ X = 0, Y = 1250 },{ X = 2000, Y = 1250 }] + }, { Name = "$WaveSeventh$", Bounty = 50, Enemies = + new ballooner { Num= 10, PosX = 1000, PosY = 0 }, + Arrows = { X = 1000, Y = 0 } + }, { Name = "$WaveEighth$", Bounty = 50, Enemies = + [new boomattack { Num= 15, Interval = 1, PosX = 500, PosY = 0 }, + new pilot { Num= 1, Interval = 80, PosX = 0, PosY = 1250 }, + new defender { Num= 3, Interval = 81, PosX = 0, PosY = 1250 }, + new bowman { Num= 3, Interval = 81, PosX = 0, PosY = 1250 }, + new pilot { Num= 1, Interval = 82, PosX = 2000, PosY = 1250 }, + new defender { Num= 3, Interval = 83, PosX = 2000, PosY = 1250 }, + new bowman { Num= 3, Interval = 83, PosX = 2000, PosY = 1250 }, + new pilot { Num= 1, Interval = 84, PosX = 200, PosY = 2000 }, + new defender { Num= 3, Interval = 85, PosX = 200, PosY = 2000 }, + new swordman { Num= 3, Interval = 85, PosX = 200, PosY = 2000 }, + new pilot { Num= 1, Interval = 86, PosX = 1800, PosY = 2000 }, + new defender { Num= 3, Interval = 87, PosX = 1800, PosY = 2000 }, + new swordman { Num= 3, Interval = 87, PosX = 1800, PosY = 2000 },], + Arrows = [{ X = 500, Y = 0 },{ X = 0, Y = 1250 },{ X = 2000, Y = 1250 },{ X = 200, Y = 2000 },{ X = 1800, Y = 2000 }] + }, { Name = "$WaveNinth$", Bounty = 100, Enemies = + [new ballooner { Num= 10, Interval = 350, PosX = 1000, PosY = 0 }, + new boomattackf { Num= 8, Interval = 1, PosX = 0, PosY = 300 }, + new boomattackf { Num= 8, Interval = 1, PosX = 2000, PosY = 300 },], + Arrows = [{ X = 1000, Y = 0 },{ X = 0, Y = 300 },{ X = 2000, Y = 300 }] + }, { Name = "$WaveTenth$", Bounty = 1000, Enemies = + [new boomattack { Num= 7, Interval = 1, PosX = 0, PosY = 0 }, + new boomattack { Num= 7, Interval = 1, PosX = 2000, PosY = 0 }, + new rocketeer { Num= 4, Interval = 10, PosX = 0, PosY = 300 }, + new rocketeer { Num= 4, Interval = 10, PosX = 2000, PosY = 300 }, + new ballooner { Num= 10, Interval = 100, PosX = 1000, PosY = 0 }, + new pilot { Num= 1, Interval = 80, PosX = 0, PosY = 1250 }, + new defender { Num= 3, Interval = 81, PosX = 0, PosY = 1250 }, + new bowman { Num= 4, Interval = 81, PosX = 0, PosY = 1250 }, + new pilot { Num= 1, Interval = 82, PosX = 2000, PosY = 1250 }, + new defender { Num= 3, Interval = 83, PosX = 2000, PosY = 1250 }, + new bowman { Num= 4, Interval = 83, PosX = 2000, PosY = 1250 }, + new pilot { Num= 1, Interval = 84, PosX = 200, PosY = 2000 }, + new defender { Num= 3, Interval = 85, PosX = 200, PosY = 2000 }, + new swordman { Num= 4, Interval = 85, PosX = 200, PosY = 2000 }, + new pilot { Num= 1, Interval = 86, PosX = 1800, PosY = 2000 }, + new defender { Num= 3, Interval = 87, PosX = 1800, PosY = 2000 }, + new swordman { Num= 4, Interval = 87, PosX = 1800, PosY = 2000 }, + new pilot { Num= 1, Interval = 88, PosX = 880, PosY = 2000 }, + new bowman { Num= 3, Interval = 89, PosX = 880, PosY = 2000 }, + new swordman { Num= 3, Interval = 89, PosX = 880, PosY = 2000 }, + new pilot { Num= 1, Interval = 88, PosX = 1120, PosY = 2000 }, + new bowman { Num= 3, Interval = 89, PosX = 1120, PosY = 2000 }, + new swordman { Num= 3, Interval = 89, PosX = 1120, PosY = 2000 },], + Arrows = [{ X = 0, Y = 0 },{ X = 2000, Y = 0 },{ X = 0, Y = 300 },{ X = 2000, Y = 300 },{ X = 1000, Y = 0 },{ X = 0, Y = 1250 },{ X = 2000, Y = 1250 },{ X = 200, Y = 2000 },{ X = 1800, Y = 2000 },{ X = 880, Y = 2000 },{ X = 1120, Y = 2000 }] + }]; + return true; +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/Sky.jpg b/planet/Defense.ocf/Windmill.ocs/Sky.jpg new file mode 100644 index 000000000..6ff99a1c8 Binary files /dev/null and b/planet/Defense.ocf/Windmill.ocs/Sky.jpg differ diff --git a/planet/Defense.ocf/Windmill.ocs/StringTblDE.txt b/planet/Defense.ocf/Windmill.ocs/StringTblDE.txt index c613df423..ac436e3e9 100644 --- a/planet/Defense.ocf/Windmill.ocs/StringTblDE.txt +++ b/planet/Defense.ocf/Windmill.ocs/StringTblDE.txt @@ -1,2 +1,49 @@ -MsgWave=%d. Angriff -MsgBoss=Bossangriff!!! \ No newline at end of file +MsgWave=Welle %d +MsgWaveCleared=Welle %d geschafft! +MsgOutOfRelaunchs=%s hat keinen Relaunch mehr. +MsgRelaunch=Relaunch %s. + +EnemyPilot=Pilot +EnemyCrewman=Matrose +EnemyDefender=Schildkämpfer +EnemyBow=Bogenschütze +EnemyBalloon=Fallschirmspringer +EnemyRocket=Raketenreiter + +WaveFirst=Nur die Vorhut +WaveSecond=Raketengeschwader +WaveThird=Raketenreiter +WaveFourth=Raketenreitergeschwader +WaveFifth=Luftschifffinte +WaveSixth=Luftwaffe +WaveSeventh=Fallschirmabwurf +WaveEighth=Schwere Luftwaffe +WaveNinth=Sonderkommando +WaveTenth=Volle Attacke + +TeamAttackers=Angreifer +TeamDefenders=Verteidiger + +HomebaseWeapons=Waffen +HomebaseItems=Gegenstände +HomebaseTechnology=Technologie +HomebaseUpgrades=Verbesserungen +HomebaseArtifacts=Artefakte + +HomebaseDescBow=Mittlere Reichweite, geringer Schaden. Endlos Pfeile. +HomebaseDescJavelin=Kurze Reichweite, hoher Schaden. Endlos Speere. +HomebaseDescMusket=Hohe Reichweite, mittlerer Schaden. Endlos Munition. +HomebaseDescGrenadeLauncher=Schleudert Bomben in die Gegner. Endlos Bomben. +HomebaseDescWindBag=Lässt Luftstöße los, die Gegner von den Füßen fegen. +HomebaseDescHammer=Zum Errichten von Verteidigungstuermen und anderen Gebaeuden. + +HomebaseLoadSpeed=Nachladezeit +HomebaseDescLoadSpeed=Verkürzt den Ladevorgang von Fernkampfwaffen (alle). +HomebaseShootingStrength=Schusskraft +HomebaseDescShootingStrength=Verstärkt die Schusskraft, wodurch die Waffen weiter schießen (außer Muskete). +HomebaseLife=Lebensenergie +HomebaseDescLife=Erhöht die Lebensenergie des Clonks. +HomebaseAdvancedWeapons=Fortgeschrittene Waffen +HomebaseDescAdvancedWeapons=Schaltet mehr Waffen und Upgrades frei. +HomebaseMasterWeapons=Meisterwaffen +HomebaseDescMasterWeapons=Schaltet mehr Waffen und Upgrades frei. \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/StringTblUS.txt b/planet/Defense.ocf/Windmill.ocs/StringTblUS.txt index 968f3e1f2..9c3bd5c75 100644 --- a/planet/Defense.ocf/Windmill.ocs/StringTblUS.txt +++ b/planet/Defense.ocf/Windmill.ocs/StringTblUS.txt @@ -1,2 +1,49 @@ -MsgWave=Attack wave %d -MsgBoss=Boss attacks!!! \ No newline at end of file +MsgWave=Wave %d +MsgWaveCleared=Wave %d done! +MsgOutOfRelaunchs=%s has no more relaunchs. +MsgRelaunch=Relaunch %s. + +EnemyPilot=Pilot +EnemyCrewman=Crewman +EnemyDefender=Shield Fighter +EnemyBow=Archer +EnemyBalloon=Parachutist +EnemyRocket=Rocketeer + +WaveFirst=Just the vanguard +WaveSecond=Rocket squadron +WaveThird=Rocket riders +WaveFourth=Rocket rider squadron +WaveFifth=Airship maneuver +WaveSixth=Air force +WaveSeventh=Parachute drop +WaveEighth=Heavy air force +WaveNinth=Task force +WaveTenth=Full attack + +TeamAttackers=Attackers +TeamDefenders=Defenders + +HomebaseWeapons=Weapons +HomebaseItems=Items +HomebaseTechnology=Technology +HomebaseUpgrades=Upgrades +HomebaseArtifacts=Artifacts + +HomebaseDescBow=Medium range, low damage. Endless arrows. +HomebaseDescJavelin=Short range, high damage. Endless javelins. +HomebaseDescMusket=High range, medium damage. Endloss bullets. +HomebaseDescGrenadeLauncher=Launches bombs at the enemies. Infinite bombs. +HomebaseDescWindBag=Releases air blasts to knock enemies of their feet. +HomebaseDescHammer=To construct defensive towers and other buildings. + +HomebaseLoadSpeed=Load speed +HomebaseDescLoadSpeed=Shortens reload time of ranged weapons (all). +HomebaseShootingStrength=Shooting strength. +HomebaseDescShootingStrength=Strengthens weapons, makes them shoot farer (except musket). +HomebaseLife=Life energy +HomebaseDescLife=Makes your clonk able to take more damage. +HomebaseAdvancedWeapons=Advanced weapons +HomebaseDescAdvancedWeapons=Unlocks more weapons and upgrades. +HomebaseMasterWeapons=Master weapons +HomebaseDescMasterWeapons=Unlocks more weapons and upgrades. \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/Airship.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/Airship.c new file mode 100644 index 000000000..5910adb20 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/Airship.c @@ -0,0 +1,60 @@ +#appendto Airship + +local health = 500; + +public func AllStop() +{ + if (GetType(this) != C4V_Def) return; + + for (var airship in FindObjects(Find_ID(Airship))) + { + airship->SetCommand(); + airship->SetXDir(); + airship->SetYDir(); + } +} + +public func PromoteNewCaptain() +{ + var crew = GetCrewMembers(); + if (!GetLength(crew)) return Sink(); + var captain = RandomElement(crew); + var fx = GetEffect("AI", captain); + if (!fx) return Sink(); // shouldn't happen + fx.weapon = this; + fx.vehicle = this; + fx.strategy = CustomAI.ExecutePilot; +} + +public func PrepareToBoard(object cpt) +{ + var crew = GetCrewMembers(); + var count = 0; + for (var member in crew) + { + var fx = GetEffect("AI", member); + if (!fx) continue; + fx.weapon = nil; + fx.strategy = nil; + fx.vehicle = nil; + fx.carrier = nil; + if (member->GetProcedure() == "PUSH") member->SetAction("Walk"); + count++; + } + // Suitable crows + if (count >= 5) + Sound("Attack"); + // Let the captain yell something + if (!Random(3) && cpt) + cpt->Message(Translate(Format("MsgAttack%d", Random(4)))); +} + +private func Sink() +{ + Incinerate(); +} + +private func GetCrewMembers() +{ + return FindObjects(Find_InRect(this.gondola[0], this.gondola[1], this.gondola[2], this.gondola[3]), Find_Owner(GetOwner()), Find_OCF(OCF_Alive)); +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/Axe.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/Axe.c new file mode 100644 index 000000000..372881b3f --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/Axe.c @@ -0,0 +1,45 @@ +#appendto Axe + +// EnemyAI calls this function when trying to attack with an axe +// Useful because circumvents tree chopping +public func ControlUse(object clonk, int iX, int iY) +{ + // Combat + if(!CanStrikeWithWeapon(clonk) || !clonk->HasHandAction()) + { + return false; + } + + var rand = Random(2)+1; + var arm = "R"; + var animation = Format("SwordSlash%d.%s", rand, arm); + carry_bone = "pos_hand2"; + + var length = this.StrikingLength; + + if(clonk->IsWalking()) + { + if(!GetEffect("AxeStrikeStop", clonk, 0)) + AddEffect("AxeStrikeStop", clonk, 2, length, this); + } + if(clonk->GetHandPosByItemPos(clonk->GetItemPos(this)) == 1) + { + arm = "L"; + carry_bone = "pos_hand1"; + animation = Format("SwordSlash%d.%s", rand, arm); + } + if(clonk->IsJumping()) + { + rand = 1; + if(clonk->GetYDir() < -5) rand = 2; + animation = Format("SwordJump%d.%s",rand,arm); + } + + PlayWeaponAnimation(clonk, animation, 10, Anim_Linear(0, 0, clonk->GetAnimationLength(animation), length, ANIM_Remove), Anim_Const(1000)); + clonk->UpdateAttach(); + + magic_number=((magic_number+1)%10) + (ObjectNumber()*10); + StartWeaponHitCheckEffect(clonk, length, 1); + + return true; +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/Balloon.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/Balloon.c new file mode 100644 index 000000000..49457577b --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/Balloon.c @@ -0,0 +1,60 @@ +#appendto Balloon + +public func ControlUseStart(object clonk) +{ + // Create the balloon and set its speed and rider. + var balloon = CreateObjectAbove(BalloonDeployed, clonk->GetX(), clonk->GetY()+5); + balloon->SetSpeed(clonk->GetXDir(), clonk->GetYDir()); + balloon->SetRider(clonk); +// balloon->SetParent(this); + + // Sound. +// Sound("Objects::Balloon::Inflate"); + + // Make the clonk ride the balloon. + clonk->SetAction("Ride", balloon); + + // Make sure clonk is not diving. + var side = ["L", "R"][Random(2)]; + clonk->PlayAnimation(Format("Jump.%s", side), CLONK_ANIM_SLOT_Movement, Anim_Linear(clonk->GetAnimationLength("Jump.L"), 0, clonk->GetAnimationLength("Jump.L"), 36, ANIM_Hold), Anim_Linear(0, 0, 1000, 5, ANIM_Remove)); + return true; +} + +public func FxControlFloatTimer(object target, proplist effect, int time) +{ + // Balloon deflates if any vertex has contact + if (GetContact(-1, CNAT_Bottom)) + { + Deflate(); + return FX_Execute_Kill; + } + return _inherited(target, effect, time, ...); +} + +private func Deflate() +{ + if (GetAction() != "Deflate") + { + SetAction("Deflate"); + SetComDir(COMD_None); + if (this.rider) + { + var fx = GetEffect("AI", this.rider); + if (!fx) return; + // Tell rider to get a new target + fx.target = nil; + this.rider->SetCommand(); + } + } +} + +public func OnProjectileHit() +{ + if (this.rider) + { + var fx = GetEffect("AI", this.rider); + if (!fx) return; + fx.parachute_lost = true; // rider must get a new target as soon as he lands + } + _inherited(...); +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/BalloonDeployed.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/BalloonDeployed.c new file mode 100644 index 000000000..e7dbf1ae3 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/BalloonDeployed.c @@ -0,0 +1,40 @@ +#appendto BalloonDeployed + +public func FxControlFloatTimer(object target, proplist effect, int time) +{ + // Balloon deflates if any vertex has contact + if (GetContact(-1, CNAT_Bottom)) + { + Deflate(); + return FX_Execute_Kill; + } + return _inherited(target, effect, time, ...); +} + +private func Deflate() +{ + if (GetAction() != "Deflate") + { + SetAction("Deflate"); + SetComDir(COMD_None); + if (this.rider) + { + var fx = GetEffect("AI", this.rider); + if (!fx) return; + // Tell rider to get a new target + fx.target = nil; + this.rider->SetCommand(); + } + } +} + +public func OnProjectileHit() +{ + if (this.rider) + { + var fx = GetEffect("AI", this.rider); + if (!fx) return; + fx.parachute_lost = true; // rider must get a new target as soon as he lands + } + _inherited(...); +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/Bow.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/Bow.c new file mode 100644 index 000000000..2494d006e --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/Bow.c @@ -0,0 +1,26 @@ +#appendto Bow + +local shooting_strength = 100; + +public func FinishedAiming(object clonk, int angle) +{ + clonk->DetachMesh(iArrowMesh); + iArrowMesh = nil; + + // shoot + if(Contents(0)) + { + if(Contents(0)->~IsArrow()) + { + var arrow = Contents(0)->TakeObject(); + arrow->Launch(angle,shooting_strength,clonk); + Sound("Objects::Weapons::Bow::Shoot?"); + } + } + + // Open the hand to let the string go and play the fire animation + PlayAnimation("Fire", 6, Anim_Linear(0, 0, GetAnimationLength("Fire"), animation_set["ShootTime"], ANIM_Hold), Anim_Const(1000)); + clonk->PlayAnimation("Close1Hand", 11, Anim_Const(0), Anim_Const(1000)); + clonk->StartShoot(this); + return true; +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/CheatArrow.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/CheatArrow.c deleted file mode 100644 index 3beee803c..000000000 --- a/planet/Defense.ocf/Windmill.ocs/System.ocg/CheatArrow.c +++ /dev/null @@ -1,26 +0,0 @@ -#appendto Arrow -#appendto LeadShot - -public func SetStackCount(int amount) -{ - count = MaxStackCount(); - UpdateStackDisplay(); -} - -public func Hit() -{ - if(GetEffect("HitCheck",this)) - RemoveObject(); -} - -public func HitObject(object obj) -{ - if(obj->GetOCF() & OCF_CrewMember) return; - inherited(obj,...); - if (this) RemoveObject(); -} - -func UpdatePicture() -{ - SetGraphics("1"); -} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/Chest_Lorry.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/Chest_Lorry.c new file mode 100644 index 000000000..a149f8120 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/Chest_Lorry.c @@ -0,0 +1,4 @@ +#appendto Lorry +#appendto Chest + +func RejectCollect() { return true; } \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/ForceVal2Array.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/ForceVal2Array.c new file mode 100644 index 000000000..f4cceeb1a --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/ForceVal2Array.c @@ -0,0 +1,3 @@ +// helper: put single elements into array + +global func ForceVal2Array(v) { if (GetType(v) != C4V_Array) return [v]; else return v; } \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/GetBoardingPoint.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/GetBoardingPoint.c new file mode 100644 index 000000000..4345cd93a --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/GetBoardingPoint.c @@ -0,0 +1,64 @@ +/* + Returns a suitable position to fly to in order to perform a boarding attack on one of the islands. + Will make enemies fly around the islands first if they spawn below them. + + Frequently used by Airship captains. +*/ + +static IslandRectangle; + +global func GetBoardingPoint(object target) +{ + if (!this) return nil; + if (!target) return nil; + + var x,y; + + // A rectangle surrounding the islands + some space (30px or so) + if (!IslandRectangle) IslandRectangle = Shape->Rectangle(780, 780, 430, 310); + + // Below the target + if (this->GetY() > target->GetY()) + { + // Below the islands + if (Inside(this->GetX(), 780, 1110)) + { + if (GetX() < LandscapeWidth()) + { + y = this->GetY() / 2; + x = RandomX(100, 200); + } else { + y = this->GetY() / 2; + x = RandomX(LandscapeWidth() - 200, LandscapeWidth() - 100); + } + } else { // way up is clear + x = GetX(); + y = 780 - Random(200); + } + } else + { + // Draw a straight line towards the target and pick a point 30 pixels away + var dist = ObjectDistance(target); + var xdist = target->GetX() - GetX(); + var ydist = target->GetY() - GetY(); + x = target->GetX() - (xdist * 30) / dist; + y = target->GetY() - (ydist * 30) / dist; + } + + // Near enough to try an attack + if (IslandRectangle->IsPointContained(this->GetX(), this->GetY())) + { + this->~PrepareToBoard(); + x = this->GetX(); + y = this->GetY(); + } + + return [x,y]; +} + +global func InsideIslandRectangle(object ship) +{ + if (!IslandRectangle) IslandRectangle = Shape->Rectangle(780, 780, 430, 310); + + return IslandRectangle->IsPointContained(ship->GetX(), ship->GetY()); +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/HitCheck.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/HitCheck.c new file mode 100644 index 000000000..e7b4b3e23 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/HitCheck.c @@ -0,0 +1,67 @@ +/* Variant of FxHitCheckDoCheck that does exclude the windmills if anything was shot by a player +*/ + +global func FxHitCheckDoCheck(object target, proplist effect) +{ + var obj; + // rather search in front of the projectile, since a hit might delete the effect, + // and clonks can effectively hide in front of walls. + var oldx = target->GetX(); + var oldy = target->GetY(); + var newx = target->GetX() + target->GetXDir() / 10; + var newy = target->GetY() + target->GetYDir() / 10; + var dist = Distance(oldx, oldy, newx, newy); + var is_human = GetPlayerType(target->GetController()) == C4PT_User; + + var shooter = effect.shooter; + var live = effect.live; + + if (live) + shooter = target; + + if (dist <= Max(1, Max(Abs(target->GetXDir()), Abs(target->GetYDir()))) * 2) + { + // We search for objects along the line on which we moved since the last check + // and sort by distance (closer first). + for (obj in FindObjects(Find_OnLine(oldx, oldy, newx, newy), + Find_NoContainer(), + Find_Layer(target->GetObjectLayer()), + Find_PathFree(target), + Sort_Distance(oldx, oldy))) + { + // Excludes + if (!obj) continue; // hit callback of one object might have removed other objects + if(obj == target) continue; + if(obj == shooter) continue; + if (is_human) { + if (obj == g_windgen1) continue; + if (obj == g_windgen2) continue; + if (obj == g_windgen3) continue; + if (obj == g_windmill) continue; + } + + // Unlike in hazard, there is no NOFF rule (yet) + // CheckEnemy + //if(!CheckEnemy(obj,target)) continue; + + // IsProjectileTarget or Alive will be hit + if (obj->~IsProjectileTarget(target, shooter) || obj->GetOCF() & OCF_Alive) + { + target->~HitObject(obj); + if (!target) + return; + } + } + } + return; +} + +// Do not reapply C4D_Object to arrows + +global func FxHitCheckStop(object target, proplist effect, int reason, bool temp) +{ + if (temp) + return; + + return; +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/Particles.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/Particles.c new file mode 100644 index 000000000..a841d3e71 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/Particles.c @@ -0,0 +1,14 @@ +global func Particles_Planks() +{ + return + { + Alpha = PV_KeyFrames(0, 0, 255, 900, 255, 1000, 0), + Size = PV_Random(20, 30), + Phase = PV_Random(0, 2), + Rotation = PV_Speed(), + ForceX = PV_Wind(50), + ForceY = PV_Gravity(100), + CollisionVertex = 500, + OnCollision = PC_Die(), + }; +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/Ropeladder.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/Ropeladder.c new file mode 100644 index 000000000..31e0547b7 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/Ropeladder.c @@ -0,0 +1,6 @@ +#appendto Ropeladder_Grabber + +public func IsInteractable(object clonk) +{ + return false; +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/Rule_ObjectFade.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/Rule_ObjectFade.c new file mode 100644 index 000000000..db0f6a6f7 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/Rule_ObjectFade.c @@ -0,0 +1,17 @@ +#appendto Rule_ObjectFade + +func Timer() +{ + for (var fade in FindObjects(Find_Or(Find_Category(C4D_Object), Find_ID(Arrow), Find_ID(Javelin)), Find_NoContainer(), Find_Not(Find_OCF(OCF_HitSpeed1)))) + { + if (!CheckFadeConditions(fade)) continue; + if (GetEffect("IntFadeOut*", fade)) continue; + AddEffect("IntFadeOutCandidate", fade, 1, 36, this, Rule_ObjectFade); + } +} + +func CheckFadeConditions(object fade) +{ + if (fade->GetID() == Airship) return true; + return _inherited(fade); +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/SensitiveWindGenerator.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/SensitiveWindGenerator.c deleted file mode 100644 index d6f78e893..000000000 --- a/planet/Defense.ocf/Windmill.ocs/System.ocg/SensitiveWindGenerator.c +++ /dev/null @@ -1,14 +0,0 @@ -#appendto WindGenerator - -func Damage() -{ - Explode(30); -} - -// No lightbulbs -func IsPowerProducer() { return false; } - -private func RegisterPowerProduction(int amount) {} - -//No triangles -func RedrawFlagRadius() { return; } \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/StringTblDE.txt b/planet/Defense.ocf/Windmill.ocs/System.ocg/StringTblDE.txt new file mode 100644 index 000000000..1216cd9b9 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/StringTblDE.txt @@ -0,0 +1,6 @@ +MsgAttack0=ANGRIFF!| +MsgAttack1=Zum Angriff!| +MsgAttack2=Attacke!| +MsgAttack3=Vorwärts!| + +MsgGotWindbag=Windbeutel jetzt verfügbar.| \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/StringTblUS.txt b/planet/Defense.ocf/Windmill.ocs/System.ocg/StringTblUS.txt new file mode 100644 index 000000000..8fd078aa3 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/StringTblUS.txt @@ -0,0 +1,6 @@ +MsgAttack0=ATTACK!| +MsgAttack1=Charge!| +MsgAttack2=Get them!| +MsgAttack3=For glory!| + +MsgGotWindbag=Windbag now available.| \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/TargetGetters.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/TargetGetters.c new file mode 100644 index 000000000..538a53a5c --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/TargetGetters.c @@ -0,0 +1,40 @@ +/* Returns a random still alive windmill */ + +global func GetRandomWindmill() +{ + if (g_lost) return nil; + + var mills = []; + if (g_windgen1) PushBack(mills, g_windgen1); + if (g_windgen2) PushBack(mills, g_windgen2); + if (g_windgen3) PushBack(mills, g_windgen3); + if (g_windmill) PushBack(mills, g_windmill); + + return RandomElement(mills); +} + +/* Returns the nearest still alive windmill */ + +global func GetNearestWindmill() +{ + if (g_lost) return nil; + if (!this) return nil; + + var mills = []; + if (g_windgen1) PushBack(mills, g_windgen1); + if (g_windgen2) PushBack(mills, g_windgen2); + if (g_windgen3) PushBack(mills, g_windgen3); + if (g_windmill) PushBack(mills, g_windmill); + + var nearest = 0, dist = LandscapeWidth(); + for (var i = 0; i < GetLength(mills); i++) + { + if (ObjectDistance(mills[i]) < dist) + { + nearest = i; + dist = ObjectDistance(mills[i]); + } + } + + return mills[nearest]; +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/WindGenerator_Windmill.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/WindGenerator_Windmill.c new file mode 100644 index 000000000..22c14c262 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/WindGenerator_Windmill.c @@ -0,0 +1,42 @@ +#appendto WindGenerator +#appendto Windmill + +// Brutal +func Death() +{ + GameCall("WindmillDown", this); + + if (GetID() == Windmill) + { + var ruin = CreateObjectAbove(Ruin_Windmill, 0, GetDefBottom()-GetY(), NO_OWNER); + ruin->MakeInvincible(); + RemoveObject(); + } else { + CreateParticle("Planks", PV_Random(-10,10), PV_Random(-30,30), PV_Random(-30,30), PV_Random(-100,-50), 500, Particles_Planks(), 15); + RemoveObject(); + } + + return true; +} + +// Overload structure library +func Damage() +{ + return; +} + +// No lightbulbs +func IsPowerProducer() { return false; } + +private func RegisterPowerProduction(int amount) {} +private func UnregisterPowerProduction(int amount) {} + +// No triangles +func RedrawFlagRadius() { return; } + +// Is objective +public func IsMainObjective() { return true; } + + +local ContactIncinerate = 0; +local BlastIncinerate = 0; \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/System.ocg/Windbag.c b/planet/Defense.ocf/Windmill.ocs/System.ocg/Windbag.c new file mode 100644 index 000000000..a6385d445 --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/System.ocg/Windbag.c @@ -0,0 +1,22 @@ +#appendto WindBag + +func Entrance(object container) +{ + if (this == g_windbag && container && container->IsClonk()) + { + var plr = container->GetOwner(); + if (GetPlayerType(plr) != C4PT_User) return _inherited(container, ...); + + var homebase = FindObject(Find_ID(Homebase), Find_Owner(plr)); + if (!homebase) return _inherited(container, ...); // ??? + + homebase->SetItemAvailable(homebase->GetEntryByID(GetID())); + g_windbag = nil; + Sound("UI::Cleared", false, nil, plr); + CustomMessage("$MsgGotWindbag$", container, plr); + var menu = FindObject(Find_ID(GUI_ObjectInteractionMenu), Find_Owner(plr)); + if (menu) Schedule(menu, "RemoveObject()", 1); + return RemoveObject(); + } + return _inherited(container, ...); +} \ No newline at end of file diff --git a/planet/Defense.ocf/Windmill.ocs/Teams.txt b/planet/Defense.ocf/Windmill.ocs/Teams.txt new file mode 100644 index 000000000..a1015b5da --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/Teams.txt @@ -0,0 +1,11 @@ +[Teams] +TeamColors=false + + [Team] + id=1 + Name=$TeamDefenders$ + + [Team] + id=2 + Name=$TeamAttackers$ + MaxPlayer=1 diff --git a/planet/Defense.ocf/Windmill.ocs/authors.txt b/planet/Defense.ocf/Windmill.ocs/authors.txt new file mode 100644 index 000000000..4726e3eff --- /dev/null +++ b/planet/Defense.ocf/Windmill.ocs/authors.txt @@ -0,0 +1,8 @@ +Sky.jpg by http://www.splitshire.com/ + CC0 + +NextWave.ogg by daveincamas (http://www.freesound.org/people/daveincamas/sounds/27082/) + CC-BY 3.0 + +Attack.ogg by Erdie (http://www.freesound.org/people/Erdie/sounds/165614/) + CC-BY 3.0 \ No newline at end of file