forked from Mirrors/openclonk
544 lines
15 KiB
C
544 lines
15 KiB
C
/**
|
|
Tutorial 04: Mining Tools
|
|
Author: Maikel
|
|
|
|
Produce loam, transport lorry, produce pickaxe and mine ore.
|
|
|
|
Following controls and concepts are explained:
|
|
* Elevator and lorry interaction.
|
|
* Production menu: foundry, workshop.
|
|
* Using items: shovel + bucket, barrel, loam, pickaxe.
|
|
*/
|
|
|
|
|
|
static guide; // guide object.
|
|
|
|
protected func Initialize()
|
|
{
|
|
// Tutorial goal.
|
|
var goal = CreateObject(Goal_Tutorial);
|
|
goal.Name = "$MsgGoalName$";
|
|
goal.Description = "$MsgGoalDescription$";
|
|
|
|
// Place objects in different sections.
|
|
InitVillageEntrance();
|
|
InitVillageMain();
|
|
InitOreMine();
|
|
InitVillageUnderground();
|
|
InitVegetation();
|
|
InitAnimals();
|
|
InitAI();
|
|
|
|
// Dialogue options -> repeat round.
|
|
SetNextScenario("Tutorials.ocf\\Tutorial04.ocs", "$MsgRepeatRound$", "$MsgRepeatRoundDesc$");
|
|
return;
|
|
}
|
|
|
|
// Gamecall from goals, set next mission.
|
|
protected func OnGoalsFulfilled()
|
|
{
|
|
// Achievement: Tutorial completed.
|
|
GainScenarioAchievement("TutorialCompleted", 3);
|
|
// Dialogue options -> next round.
|
|
SetNextScenario("Tutorials.ocf\\Tutorial05.ocs", "$MsgNextTutorial$", "$MsgNextTutorialDesc$");
|
|
// Normal scenario ending by goal library.
|
|
return false;
|
|
}
|
|
|
|
private func InitVillageEntrance()
|
|
{
|
|
// Create a signpost.
|
|
CreateObjectAbove(EnvPack_Guidepost, 56, 384);
|
|
|
|
// Wind generator and sawmill for wood production.
|
|
CreateObjectAbove(WindGenerator, 224, 384)->MakeInvincible();
|
|
CreateObjectAbove(Sawmill, 264, 386)->MakeInvincible();
|
|
|
|
// A wooden cabin ruin on fire.
|
|
CreateObjectAbove(WoodenCabin, 370, 352)->MakeInvincible();
|
|
var chest = CreateObjectAbove(Chest, 410, 352);
|
|
chest->MakeInvincible();
|
|
return;
|
|
}
|
|
|
|
private func InitOreMine()
|
|
{
|
|
// A foundry halfway down.
|
|
var foundry = CreateObjectAbove(Foundry, 456, 488);
|
|
foundry->MakeInvincible();
|
|
var lantern = foundry->CreateContents(Lantern);
|
|
lantern->TurnOn();
|
|
foundry->CreateContents(Bucket);
|
|
foundry->CreateContents(Barrel);
|
|
foundry->CreateContents(Coal, 10);
|
|
return;
|
|
}
|
|
|
|
private func InitVillageMain()
|
|
{
|
|
// Flagpole for power distribution: constructed in tutorial 3.
|
|
CreateObjectAbove(Flagpole, 496, 376);
|
|
|
|
// Elevator, and grain production on the right.
|
|
var elevator = CreateObjectAbove(Elevator, 601, 392);
|
|
elevator->CreateShaft(262);
|
|
elevator->MakeInvincible();
|
|
CreateObjectAbove(Windmill, 816, 392)->MakeInvincible();
|
|
CreateObjectAbove(Kitchen, 904, 376)->MakeInvincible();
|
|
CreateObjectAbove(Loom, 560, 392)->MakeInvincible();
|
|
Wheat->Place(40 + Random(10), Rectangle(704, 352, 80, 64));
|
|
Cotton->Place(3, Rectangle(654, 352, 40, 64));
|
|
CreateObjectAbove(StrawMan, 714, 391);
|
|
CreateObjectAbove(StrawMan, 762, 391);
|
|
|
|
// Stone door to protect the village.
|
|
var door = CreateObjectAbove(StoneDoor, 1004, 376);
|
|
var wheel = CreateObjectAbove(SpinWheel, 972, 376);
|
|
wheel->SetSwitchTarget(door);
|
|
|
|
// Tools and armory down below.
|
|
CreateObjectAbove(ToolsWorkshop, 698, 504)->MakeInvincible();
|
|
CreateObjectAbove(Armory, 772, 504)->MakeInvincible();
|
|
CreateObjectAbove(Flagpole, 828, 496)->MakeInvincible();
|
|
return;
|
|
}
|
|
|
|
private func InitVillageUnderground()
|
|
{
|
|
// A wooden cabin ruin.
|
|
CreateObjectAbove(WoodenCabin, 562, 568)->MakeInvincible();
|
|
|
|
// Chemical lab ruin and flagpole on the left of the elevator.
|
|
CreateObjectAbove(ChemicalLab, 484, 664)->MakeInvincible();
|
|
CreateObjectAbove(Flagpole, 528, 664)->MakeInvincible();
|
|
|
|
// Steam engine for power supply below.
|
|
var engine = CreateObjectAbove(SteamEngine, 780, 752);
|
|
engine->CreateContents(Coal, 5);
|
|
engine->MakeInvincible();
|
|
|
|
// Lorry with materials down below.
|
|
var lorry = CreateObjectAbove(Lorry, 954, 670);
|
|
lorry->CreateContents(Metal, 16);
|
|
lorry->CreateContents(Wood, 16);
|
|
return;
|
|
}
|
|
|
|
private func InitVegetation()
|
|
{
|
|
Grass->Place(85);
|
|
PlaceObjects(Firestone, 10 + Random(5), "Earth");
|
|
PlaceObjects(Rock, 25 + Random(5), "Earth");
|
|
Branch->Place(20, nil, {underground = true});
|
|
Flower->Place(10);
|
|
return;
|
|
}
|
|
|
|
private func InitAnimals()
|
|
{
|
|
// The wipf as your friend, controlled by AI.
|
|
var wipf = CreateObjectAbove(Wipf, 732, 392);
|
|
wipf->SetMeshMaterial("WipfSkin");
|
|
wipf->SetObjectLayer(wipf);
|
|
wipf->EnableTutorialControl();
|
|
wipf.Name = "$WipfName$";
|
|
wipf.Description = "$WipfDescription$";
|
|
|
|
// Some wipfs near the grain field.
|
|
var wipfs = Wipf->Place(4, Rectangle(640, 352, 180, 40));
|
|
for (wipf in wipfs)
|
|
{
|
|
wipf->SetObjectLayer(wipf);
|
|
wipf->EnableTutorialControl();
|
|
}
|
|
|
|
// Some butterflies as atmosphere.
|
|
Butterfly->Place(25);
|
|
return;
|
|
}
|
|
|
|
// Initializes the AI: which is all defined in System.ocg
|
|
private func InitAI()
|
|
{
|
|
// A lumberjack npc for explaining how to cut trees.
|
|
var npc_lumberjack = CreateObjectAbove(Clonk, 192, 384);
|
|
npc_lumberjack->SetColor(0xffff00);
|
|
npc_lumberjack->SetName("Sato");
|
|
npc_lumberjack->SetObjectLayer(npc_lumberjack);
|
|
npc_lumberjack->SetSkin(3);
|
|
npc_lumberjack->SetDir(DIR_Right);
|
|
npc_lumberjack->SetDialogue("Lumberjack");
|
|
|
|
// A fireman NPC who extinguishes a burning cabin.
|
|
var npc_fireman = CreateObjectAbove(Clonk, 340, 352);
|
|
npc_fireman->SetName("Hubert");
|
|
npc_fireman->SetObjectLayer(npc_fireman);
|
|
npc_fireman->SetDir(DIR_Right);
|
|
npc_fireman->SetDialogue("Fireman");
|
|
npc_fireman->SetAlternativeSkin("MaleDarkHair");
|
|
|
|
// A builder which tells you where to place the flagpole.
|
|
var npc_builder = CreateObjectAbove(Clonk, 504, 376);
|
|
npc_builder->SetName("Kummerog");
|
|
npc_builder->CreateContents(Hammer);
|
|
npc_builder->SetObjectLayer(npc_builder);
|
|
npc_builder->SetDir(DIR_Left);
|
|
npc_builder->SetDialogue("Builder");
|
|
npc_builder->SetAlternativeSkin("Carpenter");
|
|
|
|
// A farmer near the grain field.
|
|
var npc_farmer = CreateObjectAbove(Clonk, 720, 392);
|
|
npc_farmer->SetColor(0x00ffff);
|
|
npc_farmer->SetName("Genhered");
|
|
npc_farmer->SetObjectLayer(npc_farmer);
|
|
npc_farmer->SetSkin(1);
|
|
npc_farmer->SetDir(DIR_Left);
|
|
npc_farmer->SetDialogue("Farmer");
|
|
|
|
// Lookout.
|
|
var npc_lookout = CreateObjectAbove(Clonk, 992, 296);
|
|
npc_lookout->SetName("Larry");
|
|
npc_lookout->SetObjectLayer(npc_lookout);
|
|
npc_lookout->SetDir(DIR_Left);
|
|
npc_lookout->SetDialogue("Lookout");
|
|
npc_lookout->SetAlternativeSkin("Guard");
|
|
|
|
// Village head.
|
|
var npc_head = CreateObjectAbove(Clonk, 924, 672);
|
|
npc_head->SetName("Archibald");
|
|
npc_head->SetObjectLayer(npc_head);
|
|
npc_head->SetDir(DIR_Left);
|
|
npc_head->SetDialogue("VillageHead", true);
|
|
npc_head->SetAlternativeSkin("Sage");
|
|
return;
|
|
}
|
|
|
|
|
|
/*-- Player Handling --*/
|
|
|
|
protected func InitializePlayer(int plr)
|
|
{
|
|
// Position player's clonk.
|
|
var clonk = GetCrew(plr);
|
|
clonk->SetPosition(480, 366);
|
|
clonk->CreateContents(Shovel);
|
|
var effect = AddEffect("ClonkRestore", clonk, 100, 10);
|
|
effect.to_x = 480;
|
|
effect.to_y = 366;
|
|
|
|
// Claim ownership of structures.
|
|
for (var structure in FindObjects(Find_Or(Find_Category(C4D_Structure), Find_Func("IsFlagpole"))))
|
|
structure->SetOwner(plr);
|
|
|
|
// Add an effect to the clonk to track the goal.
|
|
var goal_effect = AddEffect("TrackGoal", nil, 100, 2);
|
|
goal_effect.plr = plr;
|
|
|
|
// Standard player zoom for tutorials.
|
|
SetPlayerViewLock(plr, true);
|
|
SetPlayerZoomByViewRange(plr, 400, nil, PLRZOOM_Direct | PLRZOOM_Set);
|
|
|
|
// Knowledge for the pickaxe construction.
|
|
SetPlrKnowledge(plr, Pickaxe);
|
|
|
|
// Create tutorial guide, add messages, show first.
|
|
guide = CreateObject(TutorialGuide, 0, 0, plr);
|
|
var interact = GetPlayerControlAssignment(plr, CON_Interact, true, true);
|
|
var up = GetPlayerControlAssignment(plr, CON_Up, true, true);
|
|
var down = GetPlayerControlAssignment(plr, CON_Down, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialVillageHead$", interact, up, down));
|
|
guide->ShowGuideMessage();
|
|
var effect = AddEffect("TutorialTalkedToVillageHead", nil, 100, 5);
|
|
effect.plr = plr;
|
|
return;
|
|
}
|
|
|
|
|
|
/*-- Intro, Tutorial Goal & Outro --*/
|
|
|
|
global func FxTrackGoalTimer(object target, proplist effect, int time)
|
|
{
|
|
if (effect.completed)
|
|
{
|
|
AddEffect("GoalOutro", target, 100, 5);
|
|
// Hide the tutorial guide during the outro.
|
|
guide->HideGuide();
|
|
// Start the outro sequence which fulfills the goal.
|
|
StartSequence("Outro", 0, effect.plr);
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
public func ShowLastGuideMessage()
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialCompleted$");
|
|
guide->ShowGuideMessage();
|
|
guide->ShowGuide();
|
|
return;
|
|
}
|
|
|
|
|
|
/*-- Guide Messages --*/
|
|
|
|
public func OnHasTalkedToVillageHead()
|
|
{
|
|
RemoveEffect("TutorialTalkedToVillageHead");
|
|
return;
|
|
}
|
|
|
|
global func FxTutorialTalkedToVillageHeadTimer()
|
|
{
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialTalkedToVillageHeadStop(object target, proplist effect, int reason, bool temp)
|
|
{
|
|
if (temp)
|
|
return FX_OK;
|
|
guide->AddGuideMessage("$MsgTutorialLoamProduction$");
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialGotBucket", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialGotBucketTimer(object target, proplist effect)
|
|
{
|
|
var clonk = FindObject(Find_OCF(OCF_CrewMember), Find_Owner(effect.plr));
|
|
if (clonk && FindObject(Find_ID(Bucket), Find_Container(clonk)))
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialFillBucket$");
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialFilledBucket", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialFilledBucketTimer(object target, proplist effect)
|
|
{
|
|
var clonk = FindObject(Find_OCF(OCF_CrewMember), Find_Owner(effect.plr));
|
|
if (!clonk)
|
|
return FX_OK;
|
|
var bucket = FindObject(Find_ID(Bucket), Find_Container(clonk));
|
|
if (bucket && bucket->Contents() && bucket->Contents()->~GetStackCount() >= 3)
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialGetBarrel$");
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialGotBarrel", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialGotBarrelTimer(object target, proplist effect)
|
|
{
|
|
var clonk = FindObject(Find_OCF(OCF_CrewMember), Find_Owner(effect.plr));
|
|
if (clonk && FindObject(Find_ID(Barrel), Find_Container(clonk)))
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialFillBarrel$");
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialFilledBarrel", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialFilledBarrelTimer(object target, proplist effect)
|
|
{
|
|
var foundry = FindObject(Find_ID(Foundry));
|
|
if (!foundry)
|
|
return FX_OK;
|
|
if (foundry->GetLiquidAmount("Water") >= 100)
|
|
{
|
|
var contents = GetPlayerControlAssignment(effect.plr, CON_Contents, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialProduceLoam$", contents));
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialGotLoam", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialGotLoamTimer(object target, proplist effect)
|
|
{
|
|
var clonk = FindObject(Find_OCF(OCF_CrewMember), Find_Owner(effect.plr), Find_InRect(920, 642, 40, 30));
|
|
if (!clonk)
|
|
return FX_OK;
|
|
if (FindObject(Find_ID(Loam), Find_Container(clonk)))
|
|
{
|
|
var use = GetPlayerControlAssignment(effect.plr, CON_Use, true, true);
|
|
var interact = GetPlayerControlAssignment(effect.plr, CON_Interact, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialMakeLoamBridge$", use, interact));
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialMovedLorryToWorkshop", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialMovedLorryToWorkshopTimer(object target, proplist effect)
|
|
{
|
|
if (FindObject(Find_ID(Lorry), Find_InRect(674, 474, 44, 30)))
|
|
{
|
|
var contents = GetPlayerControlAssignment(effect.plr, CON_Contents, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialProducePickaxe$", contents));
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialProducedPickaxe", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialProducedPickaxeTimer(object target, proplist effect)
|
|
{
|
|
var workshop = FindObject(Find_ID(ToolsWorkshop));
|
|
if (!workshop)
|
|
return FX_OK;
|
|
if (FindObject(Find_ID(Pickaxe), Find_Container(workshop)))
|
|
{
|
|
var use = GetPlayerControlAssignment(effect.plr, CON_Use, true, true);
|
|
guide->AddGuideMessage(Format("$MsgTutorialMineOre$", use));
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialMinedOre", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialMinedOreTimer(object target, proplist effect)
|
|
{
|
|
var workshop = FindObject(Find_ID(ToolsWorkshop));
|
|
if (!workshop)
|
|
return FX_OK;
|
|
if (ObjectCount(Find_ID(Ore), Find_InRect(260, 600, 128, 68)) >= 3)
|
|
{
|
|
guide->AddGuideMessage("$MsgTutorialPutOre$");
|
|
guide->ShowGuideMessage();
|
|
var new_effect = AddEffect("TutorialTalkedToVillageHeadFinished", nil, 100, 5);
|
|
new_effect.plr = effect.plr;
|
|
// Notify village head ore has been mined.
|
|
var dialogue_village_head = Dialogue->FindByName("VillageHead");
|
|
if (dialogue_village_head)
|
|
dialogue_village_head->SetDialogueProgress(9, nil, true);
|
|
// Move village head up.
|
|
if (dialogue_village_head)
|
|
{
|
|
var head = dialogue_village_head->GetDialogueTarget();
|
|
var head_ctrl_effect = GetEffect("IntVillageHead", head);
|
|
if (head_ctrl_effect)
|
|
head_ctrl_effect.move_up = true;
|
|
}
|
|
return FX_Execute_Kill;
|
|
}
|
|
return FX_OK;
|
|
}
|
|
|
|
public func OnHasTalkedToVillageHeadFinished()
|
|
{
|
|
RemoveEffect("TutorialTalkedToVillageHeadFinished");
|
|
return;
|
|
}
|
|
|
|
global func FxTutorialTalkedToVillageHeadFinishedTimer()
|
|
{
|
|
return FX_OK;
|
|
}
|
|
|
|
global func FxTutorialTalkedToVillageHeadFinishedStop(object target, proplist effect, int reason, bool temp)
|
|
{
|
|
if (temp)
|
|
return FX_OK;
|
|
var goal_effect = GetEffect("TrackGoal");
|
|
goal_effect.completed = true;
|
|
return FX_OK;
|
|
}
|
|
|
|
protected func OnGuideMessageShown(int plr, int index)
|
|
{
|
|
// Show the elevator case and the village head.
|
|
if (index == 0)
|
|
{
|
|
TutArrowShowTarget(FindObject(Find_ID(ElevatorCase)));
|
|
TutArrowShowTarget(FindObject(Find_ID(Clonk), Find_Not(Find_Owner(plr)), Sort_Distance(924, 672)));
|
|
}
|
|
// Show the gap and the foundry.
|
|
if (index == 1)
|
|
{
|
|
TutArrowShowPos(894, 676, 180);
|
|
TutArrowShowTarget(FindObject(Find_ID(Foundry)));
|
|
}
|
|
// Show where to dig earth.
|
|
if (index == 2)
|
|
TutArrowShowPos(504, 456, 60);
|
|
// Show the foundry.
|
|
if (index == 3)
|
|
TutArrowShowTarget(FindObject(Find_ID(Foundry)));
|
|
// Show where to dive for water.
|
|
if (index == 4)
|
|
{
|
|
TutArrowShowPos(112, 520, 200);
|
|
TutArrowShowPos(292, 512, 315);
|
|
}
|
|
// Show where to stand for loam bridge and show workshop.
|
|
if (index == 6)
|
|
{
|
|
TutArrowShowPos(916, 672, 135);
|
|
TutArrowShowTarget(FindObject(Find_ID(ToolsWorkshop)));
|
|
}
|
|
// Show ore to mine.
|
|
if (index == 8)
|
|
{
|
|
TutArrowShowPos(620, 400, 0);
|
|
TutArrowShowPos(400, 398, 240);
|
|
TutArrowShowPos(306, 628, 90);
|
|
}
|
|
return;
|
|
}
|
|
|
|
protected func OnGuideMessageRemoved(int plr, int index)
|
|
{
|
|
TutArrowClear();
|
|
return;
|
|
}
|
|
|
|
|
|
/*-- Clonk restoring --*/
|
|
|
|
|
|
global func FxClonkRestoreTimer(object target, proplist effect, int time)
|
|
{
|
|
// Respawn clonk to new location if reached certain position.
|
|
return FX_OK;
|
|
}
|
|
|
|
// Relaunches the clonk, from death or removal.
|
|
global func FxClonkRestoreStop(object target, effect, int reason, bool temporary)
|
|
{
|
|
if (reason == 3 || reason == 4)
|
|
{
|
|
var restorer = CreateObject(ObjectRestorer, 0, 0, NO_OWNER);
|
|
var x = BoundBy(target->GetX(), 0, LandscapeWidth());
|
|
var y = BoundBy(target->GetY(), 0, LandscapeHeight());
|
|
restorer->SetPosition(x, y);
|
|
var to_x = effect.to_x;
|
|
var to_y = effect.to_y;
|
|
// Respawn new clonk.
|
|
var plr = target->GetOwner();
|
|
var clonk = CreateObject(Clonk, 0, 0, plr);
|
|
clonk->GrabObjectInfo(target);
|
|
Rule_Relaunch->TransferInventory(target, clonk);
|
|
SetCursor(plr, clonk);
|
|
clonk->DoEnergy(100000);
|
|
restorer->SetRestoreObject(clonk, nil, to_x, to_y, 0, "ClonkRestore");
|
|
}
|
|
return FX_OK;
|
|
} |