Vampire clan battle gamemode (#1672)

* vampire returns + transformstions redo

* carcat fangs fix + greetings music update

* vampire skill trees

* Blood essence gathering

Introduces the vampire blood essence mechanic, including the CP14SpellVampireGatherEssence spell, new skill point consumable component, and related UI/localization updates. Adds clientside effects for spell casting, new vampire skill and action, and refines skill point gain/loss popups. Also restructures vampire components, updates spell logic for client/server prediction, and removes unused parallax files.

* perma damage

* Add skill point cost to magic system and vampire essence spell

Introduced CP14MagicEffectSkillPointCostComponent to allow magic effects to consume skill points. Updated the shared magic system to handle skill point checks and consumption. Added a new vampire spell for creating blood essence, including new icons and localization. Adjusted vampire component to grant and remove skill points, and updated related skill tree and spell prototypes. Minor fixes and refactoring in spell logic and descriptions.

* blood step + blood vision skills

* vampire clans icons

* 50 players limit + vampire objectives

* fixes

* devourers altar transmutation

* Remove StealTarget component from animal, dino, and mole NPCs

The StealTarget component and associated stealGroup were removed from boar, dinosaur, and mole NPC definitions. This likely disables these entities from being targeted for stealing, possibly to adjust gameplay balance or fix unintended behavior.

* fix

* essence creation improve

* altars

* voice masks

* transmutation fix

* teleportation glyph

* crimson candles

* candle crafting

* fix pointer predictions

* Add Vampire Clan Battle gamemode and update vampire roles

Introduces the 'Vampire Clan Battle' gamemode with new localization in English and Russian, updated game preset definitions, and secret weights. Refactors vampire antagonist briefings and objectives for multiple clans, adjusts vampire role preferences and team settings, and reduces the damage of the Vampire Gather Essence spell. Also includes minor improvements to spell and game mode descriptions, and corrects file naming for game preset locales.

* powerful kicks in

* time gates + vampire tree

* vampire proto faction

* fix

* fixes

* tree progression

* search enemy

* Update CP14SharedVampireSystem.cs

* blood essence gathering redo

* essence gathering refactor 2

* blood healing

* Update secret_weights.yml

* tree planting

* boodgrass

* tree upgrade announcement

* construction graph integration

* delete transmutation system

* workbench crafting returns

* cloaks crafting + cloak invisibility

* make vampire tree is generic red tree (sad)

* clan heart sprite

* Refactor vampire tree to clan heart system

Replaces the CP14VampireTreeComponent with CP14VampireClanHeartComponent, updating all related logic, appearance, and localization. Adjusts skill requirements, examination, and level progression to use the new clan heart system. Updates entity prototypes, visuals, and adds new orb sprites for clan heart levels. Localization strings and logic are updated to reflect the new terminology and mechanics.

* Update SpeciesBlacklist.cs

* Refactor vampire clan heart and remove tree spell

Refactored the vampire clan heart to support essence regeneration over time and adjusted level thresholds. Removed the vampire tree planting spell and related prototype fields, as well as unused tree system code. Updated localization, entity prototypes, and faction definitions to reflect these changes.

* Add clan heart construction for vampire clans

Introduces construction graphs, entities, and conditions for building unique clan hearts for each vampire clan (Unnameable, Devourers, NightChildrens). Adds new construction conditions (all clan vampires required, singleton enforcement), updates skill tree to unlock constructions, and removes the now-obsolete CP14MagicEffectAllVampireClanRequiredComponent. Also adds new frame sprites and updates localization and prototype files accordingly.

* level up vfx

* VFX + lobby track

* orb resprite

* sprites

* Add vampire altar mechanics and improve clan heart behavior

Introduces the CP14VampireAltarComponent and altar entity, which doubles blood essence extraction when victims are strapped to the altar. Adds a custom explosion behavior for vampire clan hearts upon destruction, updates construction graphs and recipes for altars, and improves localization. Also refines skill description handling and adjusts vampire bite action text.

* essence get when heart destruction

* Add clan heart damage and destruction announcements

Introduces announcements for when a vampire clan heart is damaged or destroyed, with cooldown to prevent spam. Refactors examination logic and updates localization files for both English and Russian to support new messages and sender formatting.

* glyph adaptation

* resurrection

* Add round end summary for Vampire Clan Battles

Implemented detailed round end text for the Vampire Clan Battles game mode, displaying victory, defeat, or draw outcomes based on surviving factions and population percentage. Refactored alive player percentage logic into a shared method and updated localization files with new outcome messages in English and Russian. Also removed an unused field from the defence condition component.

* Update vampire_cloak.yml

* fix

* fix

* Update portal_glyph.yml
This commit is contained in:
Red
2025-08-22 18:46:28 +03:00
committed by GitHub
parent ac52adb87a
commit 906033633e
312 changed files with 5571 additions and 881 deletions

View File

@@ -273,6 +273,21 @@ namespace Content.Client.Construction.UI
|| _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value))
continue;
//CP14 requirements
var requirementsMet = true;
foreach (var req in recipe.CP14Restrictions)
{
if (!req.Check(_entManager, _playerManager.LocalEntity.Value))
{
requirementsMet = false;
break;
}
}
if (!requirementsMet)
continue;
//CP14
if (!string.IsNullOrEmpty(search) && (recipe.Name is { } name &&
!name.Contains(search.Trim(),
StringComparison.InvariantCultureIgnoreCase)))

View File

@@ -133,6 +133,9 @@ public sealed partial class CP14NodeTreeGraphControl : BoxContainer
//Draw connection lines
foreach (var edge in _state.Edges)
{
if (!_nodeDict.ContainsKey(edge.Item1) || !_nodeDict.ContainsKey(edge.Item2))
continue;
var node1 = _nodeDict[edge.Item1];
var node2 = _nodeDict[edge.Item2];
var fromPos = node1.UiPosition * Scale + _globalOffset;

View File

@@ -218,7 +218,7 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
//Restrictions
foreach (var req in skill.Restrictions)
{
var color = req.Check(_entManager, _targetPlayer.Value, skill) ? "green" : "red";
var color = req.Check(_entManager, _targetPlayer.Value) ? "green" : "red";
sb.Append($"- [color={color}]{req.GetDescription(_entManager, _proto)}[/color]\n");
}
@@ -245,11 +245,9 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
var skillPointsMap = storage.SkillPoints;
if (skillPointsMap.TryGetValue(_selectedSkillTree.SkillType, out var skillContainer))
_window.LevelLabel.Text =
$"{Loc.GetString(indexedSkillType.Name)}: {skillContainer.Sum}/{skillContainer.Max}";
else
_window.LevelLabel.Text = $"{Loc.GetString(indexedSkillType.Name)}: 0/0";
_window.LevelLabel.Text = skillPointsMap.TryGetValue(_selectedSkillTree.SkillType, out var skillContainer)
? $"{Loc.GetString(indexedSkillType.Name)}: {skillContainer.Sum}/{skillContainer.Max}"
: $"{Loc.GetString(indexedSkillType.Name)}: 0/0";
_window.LevelTexture.Texture = indexedSkillType.Icon?.Frame0();
@@ -263,8 +261,15 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
if (skill.Tree != _selectedSkillTree)
continue;
var hide = false;
foreach (var req in skill.Restrictions)
{
if (req.HideFromUI && !req.Check(_entManager, _targetPlayer.Value))
{
hide = true;
break;
}
switch (req)
{
case NeedPrerequisite prerequisite:
@@ -279,13 +284,16 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
}
}
var nodeTreeElement = new CP14NodeTreeElement(
skill.ID,
gained: learned.Contains(skill),
active: _skill.CanLearnSkill(_targetPlayer.Value, skill),
skill.SkillUiPosition * 25f,
skill.Icon);
nodeTreeElements.Add(nodeTreeElement);
if (!hide)
{
var nodeTreeElement = new CP14NodeTreeElement(
skill.ID,
gained: learned.Contains(skill),
active: _skill.CanLearnSkill(_targetPlayer.Value, skill),
skill.SkillUiPosition * 25f,
skill.Icon);
nodeTreeElements.Add(nodeTreeElement);
}
}
_window.GraphControl.UpdateState(

View File

@@ -0,0 +1,39 @@
using Content.Shared._CP14.Vampire;
using Robust.Client.GameObjects;
using Robust.Shared.Timing;
namespace Content.Client._CP14.Vampire;
public sealed class CP14ClientVampireSystem : CP14SharedVampireSystem
{
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly IGameTiming _timing = default!;
protected override void OnVampireVisualsInit(Entity<CP14VampireVisualsComponent> vampire, ref ComponentInit args)
{
base.OnVampireVisualsInit(vampire, ref args);
if (!EntityManager.TryGetComponent(vampire, out SpriteComponent? sprite))
return;
if (_sprite.LayerMapTryGet(vampire.Owner, vampire.Comp.FangsMap, out var fangsLayerIndex, false))
_sprite.LayerSetVisible(vampire.Owner, fangsLayerIndex, true);
if (_timing.IsFirstTimePredicted)
SpawnAtPosition(vampire.Comp.EnableVFX, Transform(vampire).Coordinates);
}
protected override void OnVampireVisualsShutdown(Entity<CP14VampireVisualsComponent> vampire, ref ComponentShutdown args)
{
base.OnVampireVisualsShutdown(vampire, ref args);
if (!EntityManager.TryGetComponent(vampire, out SpriteComponent? sprite))
return;
if (_sprite.LayerMapTryGet(vampire.Owner, vampire.Comp.FangsMap, out var fangsLayerIndex, false))
_sprite.LayerSetVisible(vampire.Owner, fangsLayerIndex, false);
if (_timing.IsFirstTimePredicted)
SpawnAtPosition(vampire.Comp.DisableVFX, Transform(vampire).Coordinates);
}
}

View File

@@ -1,31 +0,0 @@
using Content.Shared._CP14.Vampire;
using Content.Shared.Humanoid;
using Robust.Client.GameObjects;
namespace Content.Client._CP14.Vampire;
public sealed class CP14ClientVampireVisualsSystem : CP14SharedVampireVisualsSystem
{
protected override void OnVampireVisualsInit(Entity<CP14VampireVisualsComponent> vampire, ref ComponentInit args)
{
base.OnVampireVisualsInit(vampire, ref args);
if (!EntityManager.TryGetComponent(vampire, out SpriteComponent? sprite))
return;
if (sprite.LayerMapTryGet(vampire.Comp.FangsMap, out var fangsLayerIndex))
sprite.LayerSetVisible(fangsLayerIndex, true);
}
protected override void OnVampireVisualsShutdown(Entity<CP14VampireVisualsComponent> vampire, ref ComponentShutdown args)
{
base.OnVampireVisualsShutdown(vampire, ref args);
if (!EntityManager.TryGetComponent(vampire, out SpriteComponent? sprite))
return;
if (sprite.LayerMapTryGet(vampire.Comp.FangsMap, out var fangsLayerIndex))
sprite.LayerSetVisible(fangsLayerIndex, false);
}
}

View File

@@ -0,0 +1,47 @@
using Content.Client.Administration.Managers;
using Content.Client.Overlays;
using Content.Shared._CP14.Vampire;
using Content.Shared._CP14.Vampire.Components;
using Content.Shared.Ghost;
using Content.Shared.StatusIcon.Components;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
namespace Content.Client._CP14.Vampire;
public sealed class CP14ShowVampireIconsSystem : EquipmentHudSystem<CP14ShowVampireFactionComponent>
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IClientAdminManager _admin = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14VampireComponent, GetStatusIconsEvent>(OnGetStatusIcons);
}
private void OnGetStatusIcons(Entity<CP14VampireComponent> ent, ref GetStatusIconsEvent args)
{
if (!_proto.TryIndex(ent.Comp.Faction, out var indexedFaction))
return;
if (!IsActive || !_proto.TryIndex(indexedFaction.FactionIcon, out var indexedIcon))
return;
// Show icons for admins
if (_admin.IsAdmin() && HasComp<GhostComponent>(_player.LocalEntity))
{
args.StatusIcons.Add(indexedIcon);
return;
}
if (TryComp<CP14ShowVampireFactionComponent>(_player.LocalEntity, out var showIcons) &&
showIcons.Faction == indexedFaction)
{
args.StatusIcons.Add(indexedIcon);
return;
}
}
}

View File

@@ -30,6 +30,9 @@ public sealed partial class AdminVerbSystem
private static readonly EntProtoId DefaultRevsRule = "Revolutionary";
private static readonly EntProtoId DefaultThiefRule = "Thief";
private static readonly ProtoId<StartingGearPrototype> PirateGearId = "PirateGear";
//CP14
private static readonly EntProtoId CP14VampireRule = "CP14GameRuleVampireClanBattle";
//CP14 end
private static readonly EntProtoId ParadoxCloneRuleId = "ParadoxCloneSpawn";
@@ -49,6 +52,21 @@ public sealed partial class AdminVerbSystem
var targetPlayer = targetActor.PlayerSession;
Verb vampire = new()
{
Text = Loc.GetString("cp14-admin-verb-text-make-vampire"),
Category = VerbCategory.Antag,
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Actions/Spells/vampire.rsi"),
"bite"),
Act = () =>
{
_antag.ForceMakeAntag<CP14VampireRuleComponent>(targetPlayer, CP14VampireRule);
},
Impact = LogImpact.High,
Message = Loc.GetString("cp14-admin-verb-make-vampire"),
};
args.Verbs.Add(vampire);
/* CP14 disable default antags
var traitorName = Loc.GetString("admin-verb-text-make-traitor");
Verb traitor = new()

View File

@@ -366,7 +366,7 @@ public sealed partial class ChatSystem : SharedChatSystem
_chatManager.ChatMessageToManyFiltered(filter, ChatChannel.Radio, message, wrappedMessage, source ?? default, false, true, colorOverride);
if (playSound)
{
_audio.PlayGlobal(announcementSound?.ToString() ?? DefaultAnnouncementSound, filter, true, AudioParams.Default.WithVolume(-2f));
_audio.PlayGlobal(announcementSound == null ? DefaultAnnouncementSound : _audio.ResolveSound(announcementSound), filter, true, AudioParams.Default.WithVolume(-2f)); //CP14 bugfix with sound resolving
}
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Station Announcement from {sender}: {message}");
}

View File

@@ -375,6 +375,17 @@ namespace Content.Server.Construction
return false;
}
//CP14 requirements
foreach (var req in constructionPrototype.CP14Restrictions)
{
if (!req.Check(EntityManager, user))
{
_popup.PopupEntity(req.GetDescription(EntityManager, PrototypeManager), user, user);
return false;
}
}
//CP14 end
var startNode = constructionGraph.Nodes[constructionPrototype.StartNode];
var targetNode = constructionGraph.Nodes[constructionPrototype.TargetNode];
var pathFind = constructionGraph.Path(startNode.Name, targetNode.Name);
@@ -460,6 +471,17 @@ namespace Content.Server.Construction
return;
}
//CP14 requirements
foreach (var req in constructionPrototype.CP14Restrictions)
{
if (!req.Check(EntityManager, user))
{
_popup.PopupEntity(req.GetDescription(EntityManager, PrototypeManager), user, user);
return;
}
}
//CP14 end
if (_container.IsEntityInContainer(user))
{
_popup.PopupEntity(Loc.GetString("construction-system-inside-container"), user, user);

View File

@@ -0,0 +1,64 @@
using Content.Shared._CP14.Vampire;
using Content.Shared._CP14.Vampire.Components;
using Content.Shared.Construction;
using Content.Shared.Examine;
using Content.Shared.Mobs.Systems;
using Content.Shared.SSDIndicator;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server._CP14.Construction.Condition;
[UsedImplicitly]
[DataDefinition]
public sealed partial class CP14AllVampireClanRequired : IGraphCondition
{
[DataField(required: true)]
public ProtoId<CP14VampireFactionPrototype> Faction;
public bool Condition(EntityUid craft, IEntityManager entityManager)
{
if (!entityManager.TryGetComponent<TransformComponent>(craft, out var craftXform))
return false;
var mobState = entityManager.System<MobStateSystem>();
var query = entityManager.EntityQueryEnumerator<CP14VampireComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var vampire, out var xform))
{
if (vampire.Faction != Faction)
continue;
if (mobState.IsDead(uid))
continue;
if (entityManager.TryGetComponent<SSDIndicatorComponent>(uid, out var ssd) && ssd.IsSSD)
continue;
//Check distance to the vampire
if (!xform.Coordinates.TryDistance(entityManager, craftXform.Coordinates, out var distance) || distance > 2)
return false;
}
return true;
}
public bool DoExamine(ExaminedEvent args)
{
if (Condition(args.Examined, IoCManager.Resolve<IEntityManager>()))
return false;
args.PushMarkup(Loc.GetString("cp14-magic-spell-need-all-vampires"));
return true;
}
public IEnumerable<ConstructionGuideEntry> GenerateGuideEntry()
{
yield return new ConstructionGuideEntry()
{
Localization = "cp14-magic-spell-need-all-vampires",
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Actions/Spells/vampire.rsi"), "blood_moon"),
};
}
}

View File

@@ -0,0 +1,43 @@
using Content.Shared._CP14.UniqueLoot;
using Content.Shared.Construction;
using Content.Shared.Examine;
using JetBrains.Annotations;
namespace Content.Server._CP14.Construction.Condition;
[UsedImplicitly]
[DataDefinition]
public sealed partial class CP14SingletonNotExist : IGraphCondition
{
[DataField(required: true)]
public string Key = string.Empty;
public bool Condition(EntityUid craft, IEntityManager entityManager)
{
var query = entityManager.EntityQueryEnumerator<CP14SingletonComponent>();
while (query.MoveNext(out var uid, out var singleton))
{
if (singleton.Key == Key)
return false;
}
return true;
}
public bool DoExamine(ExaminedEvent args)
{
if (Condition(args.Examined, IoCManager.Resolve<IEntityManager>()))
return false;
args.PushMarkup(Loc.GetString("cp14-construction-condition-singleton"));
return true;
}
public IEnumerable<ConstructionGuideEntry> GenerateGuideEntry()
{
yield return new ConstructionGuideEntry()
{
Localization = "cp14-construction-condition-singleton",
};
}
}

View File

@@ -1,96 +1,87 @@
using System.Linq;
using Content.Server._CP14.GameTicking.Rules.Components;
using Content.Server._CP14.Vampire;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server._CP14.Objectives.Systems;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Shared._CP14.Vampire;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Popups;
using Robust.Shared.Timing;
using Content.Shared._CP14.Vampire.Components;
using Content.Shared.GameTicking.Components;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.GameTicking.Rules;
public sealed class CP14VampireRuleSystem : GameRuleSystem<CP14VampireRuleComponent>
{
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly TemperatureSystem _temperature = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly FlammableSystem _flammable = default!;
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly CP14VampireObjectiveConditionsSystem _condition = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public override void Initialize()
protected override void AppendRoundEndText(EntityUid uid,
CP14VampireRuleComponent component,
GameRuleComponent gameRule,
ref RoundEndTextAppendEvent args)
{
base.Initialize();
base.AppendRoundEndText(uid, component, gameRule, ref args);
SubscribeLocalEvent<CP14VampireComponent, MapInitEvent>(OnVampireInit);
SubscribeLocalEvent<CP14VampireComponent, CP14HungerChangedEvent>(OnVampireHungerChanged);
}
//Alive percentage
var alivePercentage = _condition.CalculateAlivePlayersPercentage();
private void OnVampireHungerChanged(Entity<CP14VampireComponent> ent, ref CP14HungerChangedEvent args)
{
if (args.NewThreshold == HungerThreshold.Starving || args.NewThreshold == HungerThreshold.Dead)
var aliveFactions = new HashSet<ProtoId<CP14VampireFactionPrototype>>();
var query = EntityQueryEnumerator<CP14VampireComponent, MobStateComponent>();
while (query.MoveNext(out var vampireUid, out var vampire, out var mobState))
{
RevealVampire(ent);
if (mobState.CurrentState != MobState.Alive)
continue;
if (vampire.Faction is null)
continue;
aliveFactions.Add(vampire.Faction.Value);
}
args.AddLine($"[head=2][color=#ab1b3d]{Loc.GetString("cp14-vampire-clans-battle")}[/color][/head]");
if (alivePercentage > _condition.RequiredAlivePercentage)
{
if (aliveFactions.Count == 0)
{
//City win
args.AddLine($"[head=3][color=#7d112b]{Loc.GetString("cp14-vampire-clans-battle-clan-city-win")}[/color][/head]");
args.AddLine(Loc.GetString("cp14-vampire-clans-battle-clan-city-win-desc"));
}
if (aliveFactions.Count == 1)
{
var faction = aliveFactions.First();
if (_proto.TryIndex(faction, out var indexedFaction))
args.AddLine($"[head=3][color=#7d112b]{Loc.GetString("cp14-vampire-clans-battle-clan-win", ("name", Loc.GetString(indexedFaction.Name)))}[/color][/head]");
args.AddLine(Loc.GetString("cp14-vampire-clans-battle-clan-win-desc"));
}
if (aliveFactions.Count == 2)
{
var factions = aliveFactions.ToArray();
if (_proto.TryIndex(factions[0], out var indexedFaction1) && _proto.TryIndex(factions[1], out var indexedFaction2))
args.AddLine($"[head=3][color=#7d112b]{Loc.GetString("cp14-vampire-clans-battle-clan-tie-2", ("name1", Loc.GetString(indexedFaction1.Name)), ("name2", Loc.GetString(indexedFaction2.Name)))}[/color][/head]");
args.AddLine(Loc.GetString("cp14-vampire-clans-battle-clan-tie-2-desc"));
}
if (aliveFactions.Count == 3)
{
args.AddLine($"[head=3][color=#7d112b]{Loc.GetString("cp14-vampire-clans-battle-clan-tie-3")}[/color][/head]");
args.AddLine(Loc.GetString("cp14-vampire-clans-battle-clan-tie-3-desc"));
}
}
else
{
HideVampire(ent);
args.AddLine($"[head=3][color=#7d112b]{Loc.GetString("cp14-vampire-clans-battle-clan-lose")}[/color][/head]");
args.AddLine(Loc.GetString("cp14-vampire-clans-battle-clan-lose-desc"));
}
}
private void RevealVampire(Entity<CP14VampireComponent> ent)
{
EnsureComp<CP14VampireVisualsComponent>(ent);
}
private void HideVampire(Entity<CP14VampireComponent> ent)
{
RemCompDeferred<CP14VampireVisualsComponent>(ent);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<CP14VampireComponent, TemperatureComponent, FlammableComponent>();
while (query.MoveNext(out var uid, out var vampire, out var temperature, out var flammable))
{
if (_timing.CurTime < vampire.NextHeatTime)
continue;
vampire.NextHeatTime = _timing.CurTime + vampire.HeatFrequency;
return;
//if (!_dayCycle.UnderSunlight(uid))
// continue;
_temperature.ChangeHeat(uid, vampire.HeatUnderSunTemperature);
_popup.PopupEntity(Loc.GetString("cp14-heat-under-sun"), uid, uid, PopupType.SmallCaution);
if (temperature.CurrentTemperature > vampire.IgniteThreshold && !flammable.OnFire)
{
_flammable.AdjustFireStacks(uid, 1, flammable);
_flammable.Ignite(uid, uid, flammable);
}
}
}
private void OnVampireInit(Entity<CP14VampireComponent> ent, ref MapInitEvent args)
{
_bloodstream.ChangeBloodReagent(ent.Owner, ent.Comp.NewBloodReagent);
foreach (var (organUid, _) in _body.GetBodyOrgans(ent))
{
if (TryComp<MetabolizerComponent>(organUid, out var metabolizer) && metabolizer.MetabolizerTypes is not null)
{
metabolizer.MetabolizerTypes.Add(ent.Comp.MetabolizerType);
}
}
args.AddLine(Loc.GetString("cp14-vampire-clans-battle-alive-people", ("percent", alivePercentage * 100)));
}
}

View File

@@ -45,8 +45,6 @@ public sealed class CP14MagicSystem : CP14SharedMagicSystem
SubscribeLocalEvent<CP14MagicEffectCastingVisualComponent, CP14StartCastMagicEffectEvent>(OnSpawnMagicVisualEffect);
SubscribeLocalEvent<CP14MagicEffectCastingVisualComponent, CP14EndCastMagicEffectEvent>(OnDespawnMagicVisualEffect);
SubscribeLocalEvent<CP14MagicEffectManaCostComponent, CP14MagicEffectConsumeResourceEvent>(OnManaConsume);
SubscribeLocalEvent<CP14MagicEffectRequiredMusicToolComponent, CP14CastMagicEffectAttemptEvent>(OnMusicCheck);
}
@@ -142,29 +140,6 @@ public sealed class CP14MagicSystem : CP14SharedMagicSystem
ent.Comp.SpawnedEntity = null;
}
private void OnManaConsume(Entity<CP14MagicEffectManaCostComponent> ent, ref CP14MagicEffectConsumeResourceEvent args)
{
if (!TryComp<CP14MagicEffectComponent>(ent, out var magicEffect))
return;
var requiredMana = CalculateManacost(ent, args.Performer);
//First - used object
if (magicEffect.SpellStorage is not null && TryComp<CP14MagicEnergyContainerComponent>(magicEffect.SpellStorage, out var magicStorage))
{
var spellEv = new CP14SpellFromSpellStorageUsedEvent(args.Performer, (ent, magicEffect), requiredMana);
RaiseLocalEvent(magicEffect.SpellStorage.Value, ref spellEv);
_magicEnergy.ChangeEnergy((magicEffect.SpellStorage.Value, magicStorage), -requiredMana, out var changedEnergy, out var overloadedEnergy, safe: false);
requiredMana -= FixedPoint2.Abs(changedEnergy + overloadedEnergy);
}
//Second - action user
if (requiredMana > 0 &&
TryComp<CP14MagicEnergyContainerComponent>(args.Performer, out var playerMana))
_magicEnergy.ChangeEnergy((args.Performer.Value, playerMana), -requiredMana, out _, out _, safe: false);
}
private void OnMusicCheck(Entity<CP14MagicEffectRequiredMusicToolComponent> ent, ref CP14CastMagicEffectAttemptEvent args)
{
var passed = false;

View File

@@ -0,0 +1,17 @@
using Content.Server._CP14.Objectives.Systems;
using Content.Shared._CP14.Vampire;
using Content.Shared.StatusIcon;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server._CP14.Objectives.Components;
[RegisterComponent, Access(typeof(CP14VampireObjectiveConditionsSystem))]
public sealed partial class CP14VampireBloodPurityConditionComponent : Component
{
[DataField]
public ProtoId<CP14VampireFactionPrototype>? Faction;
[DataField]
public SpriteSpecifier Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Actions/Spells/vampire.rsi"), "blood_moon");
}

View File

@@ -0,0 +1,11 @@
using Content.Server._CP14.Objectives.Systems;
using Robust.Shared.Utility;
namespace Content.Server._CP14.Objectives.Components;
[RegisterComponent, Access(typeof(CP14VampireObjectiveConditionsSystem))]
public sealed partial class CP14VampireDefenceVillageConditionComponent : Component
{
[DataField]
public SpriteSpecifier Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/_CP14/Actions/Spells/vampire.rsi"), "essence_create");
}

View File

@@ -1,14 +1,9 @@
using Content.Server._CP14.Objectives.Components;
using Content.Server.Cargo.Systems;
using Content.Server.Objectives.Components;
using Content.Shared._CP14.Currency;
using Content.Shared.Interaction;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Objectives.Components;
using Content.Shared.Objectives.Systems;
using Robust.Shared.Containers;
namespace Content.Server._CP14.Objectives.Systems;

View File

@@ -0,0 +1,110 @@
using Content.Server._CP14.Objectives.Components;
using Content.Server.Station.Components;
using Content.Shared._CP14.Vampire.Components;
using Content.Shared.Mind;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Objectives.Components;
using Content.Shared.Objectives.Systems;
using Content.Shared.Roles.Jobs;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Objectives.Systems;
public sealed class CP14VampireObjectiveConditionsSystem : EntitySystem
{
[Dependency] private readonly MetaDataSystem _meta = default!;
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedJobSystem _jobs = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
public readonly float RequiredAlivePercentage = 0.5f;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14VampireBloodPurityConditionComponent, ObjectiveAfterAssignEvent>(OnBloodPurityAfterAssign);
SubscribeLocalEvent<CP14VampireBloodPurityConditionComponent, ObjectiveGetProgressEvent>(OnBloodPurityGetProgress);
SubscribeLocalEvent<CP14VampireDefenceVillageConditionComponent, ObjectiveAfterAssignEvent>(OnDefenceAfterAssign);
SubscribeLocalEvent<CP14VampireDefenceVillageConditionComponent, ObjectiveGetProgressEvent>(OnDefenceGetProgress);
}
private void OnDefenceAfterAssign(Entity<CP14VampireDefenceVillageConditionComponent> ent, ref ObjectiveAfterAssignEvent args)
{
_meta.SetEntityName(ent, Loc.GetString("cp14-objective-vampire-defence-settlement-title"));
_meta.SetEntityDescription(ent, Loc.GetString("cp14-objective-vampire-defence-settlement-desc", ("count", RequiredAlivePercentage * 100)));
_objectives.SetIcon(ent, ent.Comp.Icon);
}
public float CalculateAlivePlayersPercentage()
{
var query = EntityQueryEnumerator<StationJobsComponent>();
var totalPlayers = 0f;
var alivePlayers = 0f;
while (query.MoveNext(out var uid, out var jobs))
{
totalPlayers += jobs.PlayerJobs.Count;
foreach (var (netUserId, jobsList) in jobs.PlayerJobs)
{
if (!_mind.TryGetMind(netUserId, out var mind))
continue;
if (!_jobs.MindTryGetJob(mind, out var jobRole))
continue;
var firstMindEntity = GetEntity(mind.Value.Comp.OriginalOwnedEntity);
if (firstMindEntity is null)
continue;
if (!_mobState.IsDead(firstMindEntity.Value))
alivePlayers++;
}
}
return totalPlayers > 0 ? (alivePlayers / totalPlayers) : 0f;
}
private void OnDefenceGetProgress(Entity<CP14VampireDefenceVillageConditionComponent> ent, ref ObjectiveGetProgressEvent args)
{
args.Progress = CalculateAlivePlayersPercentage() > RequiredAlivePercentage ? 1 : 0;
}
private void OnBloodPurityAfterAssign(Entity<CP14VampireBloodPurityConditionComponent> ent, ref ObjectiveAfterAssignEvent args)
{
if (!TryComp<CP14VampireComponent>(args.Mind?.OwnedEntity, out var vampireComp))
return;
ent.Comp.Faction = vampireComp.Faction;
_meta.SetEntityName(ent, Loc.GetString("cp14-objective-vampire-pure-bood-title"));
_meta.SetEntityDescription(ent, Loc.GetString("cp14-objective-vampire-pure-bood-desc"));
_objectives.SetIcon(ent, ent.Comp.Icon);
}
private void OnBloodPurityGetProgress(Entity<CP14VampireBloodPurityConditionComponent> ent, ref ObjectiveGetProgressEvent args)
{
var query = EntityQueryEnumerator<CP14VampireComponent, MobStateComponent>();
while (query.MoveNext(out var uid, out var vampire, out var mobState))
{
if (vampire.Faction != ent.Comp.Faction)
{
if (mobState.CurrentState == MobState.Dead)
continue;
args.Progress = 0f;
return;
}
}
args.Progress = 1f;
}
}

View File

@@ -2,7 +2,6 @@ using System.Linq;
using Content.Shared._CP14.UniqueLoot;
using Content.Shared.GameTicking;
using Content.Shared.Tag;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
@@ -23,10 +22,24 @@ public sealed partial class CP14UniqueLootSystem : EntitySystem
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnCleanup);
SubscribeLocalEvent<CP14UniqueLootSpawnerComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<CP14SingletonComponent, MapInitEvent>(OnSingletonMapInit);
RefreshUniqueLoot();
}
private void OnSingletonMapInit(Entity<CP14SingletonComponent> ent, ref MapInitEvent args)
{
var query = EntityQueryEnumerator<CP14SingletonComponent>();
while (query.MoveNext(out var existingEnt, out var existingComp))
{
if (existingEnt == ent.Owner || existingComp.Key != ent.Comp.Key)
continue;
// Remove the existing entity with the same key.
QueueDel(existingEnt);
}
}
private void OnMapInit(Entity<CP14UniqueLootSpawnerComponent> ent, ref MapInitEvent args)
{
var loot = GetNextUniqueLoot(ent.Comp.Tag);

View File

@@ -0,0 +1,37 @@
using System.Numerics;
using Content.Server.Destructible;
using Content.Server.Destructible.Thresholds.Behaviors;
using Content.Shared._CP14.Vampire.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Server._CP14.Vampire;
[Serializable]
[DataDefinition]
public sealed partial class CP14VampireAltarExplodeBehavior : IThresholdBehavior
{
[DataField]
public EntProtoId Essence = "CP14BloodEssence";
[DataField]
public float ExtractionPercentage = 0.5f;
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{
if(!system.EntityManager.TryGetComponent<TransformComponent>(owner, out var transform))
return;
if(!system.EntityManager.TryGetComponent<CP14VampireClanHeartComponent>(owner, out var clanHeart))
return;
var random = IoCManager.Resolve<IRobustRandom>();
var collected = clanHeart.CollectedEssence;
var spawnedEssence = MathF.Floor(collected.Float() * ExtractionPercentage);
for (var i = 0; i < spawnedEssence; i++)
{
system.EntityManager.SpawnAtPosition(Essence, transform.Coordinates.Offset(new Vector2(random.NextFloat(-1f, 1f), random.NextFloat(-1f, 1f))));
}
}
}

View File

@@ -1,29 +0,0 @@
using Content.Server._CP14.GameTicking.Rules;
using Content.Shared.Body.Prototypes;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Vampire;
[RegisterComponent]
[Access(typeof(CP14VampireRuleSystem))]
public sealed partial class CP14VampireComponent : Component
{
[DataField]
public ProtoId<ReagentPrototype> NewBloodReagent = "CP14BloodVampire";
[DataField]
public ProtoId<MetabolizerTypePrototype> MetabolizerType = "CP14Vampire";
[DataField]
public float HeatUnderSunTemperature = 12000f;
[DataField]
public TimeSpan HeatFrequency = TimeSpan.FromSeconds(1);
[DataField]
public TimeSpan NextHeatTime = TimeSpan.Zero;
[DataField]
public float IgniteThreshold = 350f;
}

View File

@@ -0,0 +1,102 @@
using System.Text;
using Content.Server.Chat.Systems;
using Content.Shared._CP14.Vampire;
using Content.Shared._CP14.Vampire.Components;
using Content.Shared.Damage;
using Content.Shared.Destructible;
using Content.Shared.Examine;
using Content.Shared.Ghost;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Vampire;
public sealed partial class CP14VampireSystem
{
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
private void InitializeAnnounces()
{
SubscribeLocalEvent<CP14VampireClanHeartComponent, DamageChangedEvent>(OnHeartDamaged);
SubscribeLocalEvent<CP14VampireClanHeartComponent, ComponentRemove>(OnHeartDestructed);
}
private void OnHeartDamaged(Entity<CP14VampireClanHeartComponent> ent, ref DamageChangedEvent args)
{
if (ent.Comp.Faction is null)
return;
if (!args.DamageIncreased)
return;
if (_timing.CurTime < ent.Comp.NextAnnounceTime)
return;
ent.Comp.NextAnnounceTime = _timing.CurTime + ent.Comp.MaxAnnounceFreq;
AnnounceToFaction(ent.Comp.Faction.Value, Loc.GetString("cp14-vampire-tree-damaged"));
}
private void OnHeartDestructed(Entity<CP14VampireClanHeartComponent> ent, ref ComponentRemove args)
{
if (ent.Comp.Faction is null)
return;
if (!Proto.TryIndex(ent.Comp.Faction, out var indexedFaction))
return;
AnnounceToFaction(ent.Comp.Faction.Value, Loc.GetString("cp14-vampire-tree-destroyed-self"));
AnnounceToOpposingFactions(ent.Comp.Faction.Value, Loc.GetString("cp14-vampire-tree-destroyed", ("name", Loc.GetString(indexedFaction.Name))));
}
public void AnnounceToFaction(ProtoId<CP14VampireFactionPrototype> faction, string message)
{
var filter = Filter.Empty();
var query = EntityQueryEnumerator<CP14VampireComponent, ActorComponent>();
while (query.MoveNext(out var uid, out var vampire, out var actor))
{
if (vampire.Faction != faction)
continue;
filter.AddPlayer(actor.PlayerSession);
}
if (filter.Count == 0)
return;
VampireAnnounce(filter, message);
}
public void AnnounceToOpposingFactions(ProtoId<CP14VampireFactionPrototype> faction, string message)
{
var filter = Filter.Empty();
var query = EntityQueryEnumerator<CP14VampireComponent, ActorComponent>();
while (query.MoveNext(out var uid, out var vampire, out var actor))
{
if (vampire.Faction == faction)
continue;
filter.AddPlayer(actor.PlayerSession);
}
if (filter.Count == 0)
return;
VampireAnnounce(filter, message);
}
private void VampireAnnounce(Filter players, string message)
{
_chat.DispatchFilteredAnnouncement(
players,
message,
sender: Loc.GetString("cp14-vampire-sender"),
announcementSound: new SoundPathSpecifier("/Audio/_CP14/Announce/vampire.ogg"),
colorOverride: Color.FromHex("#820e22"));
}
}

View File

@@ -0,0 +1,176 @@
using System.Text;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Shared._CP14.DayCycle;
using Content.Shared._CP14.Vampire;
using Content.Shared._CP14.Vampire.Components;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.Ghost;
using Content.Shared.Popups;
using Content.Shared.Stacks;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Physics.Events;
using Robust.Shared.Timing;
namespace Content.Server._CP14.Vampire;
public sealed partial class CP14VampireSystem : CP14SharedVampireSystem
{
[Dependency] private readonly BodySystem _body = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly TemperatureSystem _temperature = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly FlammableSystem _flammable = default!;
[Dependency] private readonly CP14DayCycleSystem _dayCycle = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
public override void Initialize()
{
base.Initialize();
InitializeAnnounces();
SubscribeLocalEvent<CP14VampireClanHeartComponent, StartCollideEvent>(OnStartCollide);
SubscribeLocalEvent<CP14VampireClanHeartComponent, ExaminedEvent>(OnExamined);
}
private void OnStartCollide(Entity<CP14VampireClanHeartComponent> ent, ref StartCollideEvent args)
{
if (!TryComp<CP14VampireTreeCollectableComponent>(args.OtherEntity, out var collectable))
return;
var collect = collectable.Essence;
if (TryComp<StackComponent>(args.OtherEntity, out var stack))
collect *= stack.Count;
AddEssence(ent, collect);
Del(args.OtherEntity);
_audio.PlayPvs(collectable.CollectSound, ent);
}
private void OnExamined(Entity<CP14VampireClanHeartComponent> ent, ref ExaminedEvent args)
{
if (!HasComp<CP14VampireComponent>(args.Examiner) && !HasComp<GhostComponent>(args.Examiner))
return;
var sb = new StringBuilder();
// Faction
if (Proto.TryIndex(ent.Comp.Faction, out var indexedFaction))
sb.Append(Loc.GetString("cp14-vampire-tree-examine-faction", ("faction", Loc.GetString(indexedFaction.Name))) + "\n");
// Are they friend or foe?
if (TryComp<CP14VampireComponent>(args.Examiner, out var examinerVampire))
{
if (examinerVampire.Faction == ent.Comp.Faction)
sb.Append(Loc.GetString("cp14-vampire-tree-examine-friend") + "\n");
else
sb.Append(Loc.GetString("cp14-vampire-tree-examine-enemy") + "\n");
}
//Progress
sb.Append(Loc.GetString("cp14-vampire-tree-examine-level",
("level", ent.Comp.Level),
("essence", ent.Comp.EssenceFromLevelStart),
("left", ent.Comp.EssenceToNextLevel?.ToString() ?? "∞")) + "\n"+ "\n");
var query = EntityQueryEnumerator<CP14VampireClanHeartComponent>();
sb.Append(Loc.GetString("cp14-vampire-tree-other-title") + "\n");
while (query.MoveNext(out var uid, out var heart))
{
if (uid == ent.Owner)
continue;
if (!Proto.TryIndex(heart.Faction, out var indexedOtherFaction))
continue;
sb.Append(Loc.GetString("cp14-vampire-tree-other-info",
("name", Loc.GetString(indexedOtherFaction.Name)),
("essence", heart.EssenceFromLevelStart),
("left", heart.EssenceToNextLevel?.ToString() ?? "∞"),
("lvl", heart.Level)) + "\n");
}
args.PushMarkup(sb.ToString());
}
private void AddEssence(Entity<CP14VampireClanHeartComponent> ent, FixedPoint2 amount)
{
if (!Proto.TryIndex(ent.Comp.Faction, out var indexedFaction) || ent.Comp.Faction == null)
return;
var level = ent.Comp.Level;
ent.Comp.CollectedEssence += amount;
Dirty(ent);
if (level < ent.Comp.Level) //Level up!
{
_appearance.SetData(ent, VampireClanLevelVisuals.Level, ent.Comp.Level);
AnnounceToOpposingFactions(ent.Comp.Faction.Value, Loc.GetString("cp14-vampire-tree-growing", ("name", Loc.GetString(indexedFaction.Name)), ("level", ent.Comp.Level)));
AnnounceToFaction(ent.Comp.Faction.Value, Loc.GetString("cp14-vampire-tree-growing-self", ("level", ent.Comp.Level)));
SpawnAtPosition(ent.Comp.LevelUpVfx, Transform(ent).Coordinates);
}
}
protected override void OnVampireInit(Entity<CP14VampireComponent> ent, ref MapInitEvent args)
{
base.OnVampireInit(ent, ref args);
//Metabolism
foreach (var (organUid, _) in _body.GetBodyOrgans(ent))
{
if (TryComp<MetabolizerComponent>(organUid, out var metabolizer) && metabolizer.MetabolizerTypes is not null)
{
metabolizer.MetabolizerTypes.Add(ent.Comp.MetabolizerType);
}
}
}
public override void Update(float frameTime)
{
base.Update(frameTime);
//Vampire under sun heating
var query = EntityQueryEnumerator<CP14VampireComponent, CP14VampireVisualsComponent, TemperatureComponent, FlammableComponent>();
while (query.MoveNext(out var uid, out var vampire, out var visuals, out var temperature, out var flammable))
{
if (_timing.CurTime < vampire.NextHeatTime)
continue;
vampire.NextHeatTime = _timing.CurTime + vampire.HeatFrequency;
if (!_dayCycle.UnderSunlight(uid))
continue;
_temperature.ChangeHeat(uid, vampire.HeatUnderSunTemperature);
_popup.PopupEntity(Loc.GetString("cp14-heat-under-sun"), uid, uid, PopupType.SmallCaution);
if (temperature.CurrentTemperature > vampire.IgniteThreshold && !flammable.OnFire)
{
_flammable.AdjustFireStacks(uid, 1, flammable);
_flammable.Ignite(uid, uid, flammable);
}
}
var heartQuery = EntityQueryEnumerator<CP14VampireClanHeartComponent>();
//regen essence over time
while (heartQuery.MoveNext(out var uid, out var heart))
{
if (_timing.CurTime < heart.NextRegenTime)
continue;
heart.NextRegenTime = _timing.CurTime + heart.RegenFrequency;
AddEssence((uid, heart), heart.EssenceRegenPerLevel * heart.Level);
}
}
}

View File

@@ -1,7 +0,0 @@
using Content.Shared._CP14.Vampire;
namespace Content.Server._CP14.Vampire;
public sealed class CP14VampireVisualsSystem : CP14SharedVampireVisualsSystem
{
}

View File

@@ -1,3 +1,4 @@
using Content.Shared._CP14.Skill.Restrictions;
using Content.Shared.Construction.Conditions;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
@@ -62,6 +63,12 @@ public sealed partial class ConstructionPrototype : IPrototype
[DataField]
public EntityWhitelist? EntityWhitelist { get; private set; }
/// <summary>
/// CP14 - Some recipes are not available until certain conditions are met.
/// </summary>
[DataField]
public List<CP14SkillRestriction> CP14Restrictions = new();
[DataField] public string Category { get; private set; } = string.Empty;
[DataField("objectType")] public ConstructionType Type { get; private set; } = ConstructionType.Structure;

View File

@@ -130,11 +130,6 @@ public sealed class HungerSystem : EntitySystem
if (calculatedHungerThreshold == component.CurrentThreshold)
return;
//CP14 Raise hunger event for vampire
var ev = new CP14HungerChangedEvent(component.CurrentThreshold, calculatedHungerThreshold);
RaiseLocalEvent(uid, ev);
//CP14 Raise hunger event for vampire end
component.CurrentThreshold = calculatedHungerThreshold;
DirtyField(uid, component, nameof(HungerComponent.CurrentThreshold));
DoHungerThresholdEffects(uid, component);
@@ -281,10 +276,3 @@ public sealed class HungerSystem : EntitySystem
}
}
}
public sealed class CP14HungerChangedEvent(HungerThreshold oldThreshold, HungerThreshold newThreshold) : EntityEventArgs
{
public HungerThreshold OldThreshold { get; } = oldThreshold;
public HungerThreshold NewThreshold { get; } = newThreshold;
}

View File

@@ -12,4 +12,10 @@ public sealed partial class CCVars
/// </summary>
public static readonly CVarDef<bool> CP14ClosedBetaTest =
CVarDef.Create("cp14.closet_beta_test", false, CVar.SERVERONLY);
/// <summary>
/// Should powerful spells be restricted from being learned until a certain time has elapsed?
/// </summary>
public static readonly CVarDef<bool>
CP14SkillTimers = CVarDef.Create("cp14.skill_timers", true, CVar.SERVER | CVar.REPLICATED);
}

View File

@@ -59,6 +59,7 @@ public abstract partial class CP14SharedFarmingSystem : EntitySystem
ent.Comp.Energy = MathHelper.Clamp(ent.Comp.Energy + energyDelta, 0, ent.Comp.EnergyMax);
Dirty(ent);
}
public void AffectResource(Entity<CP14PlantComponent> ent, float resourceDelta)
{
if (resourceDelta == 0)

View File

@@ -38,8 +38,11 @@ public sealed partial class CP14PlantComponent : Component
[DataField, AutoPausedField]
public TimeSpan NextUpdateTime = TimeSpan.Zero;
[DataField(required: true)]
public string Solution = string.Empty;
/// <summary>
/// Solution for metabolizing resources
/// </summary>
[DataField]
public string? Solution;
}
/// <summary>

View File

@@ -1,10 +1,14 @@
using System.Linq;
using Content.Shared._CP14.MagicEnergy.Components;
using Content.Shared._CP14.MagicSpell.Components;
using Content.Shared._CP14.MagicSpell.Events;
using Content.Shared._CP14.Religion.Components;
using Content.Shared._CP14.Religion.Systems;
using Content.Shared._CP14.Skill;
using Content.Shared._CP14.Skill.Components;
using Content.Shared.CombatMode.Pacification;
using Content.Shared.Damage.Components;
using Content.Shared.FixedPoint;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Mobs;
@@ -18,6 +22,7 @@ public abstract partial class CP14SharedMagicSystem
{
[Dependency] private readonly CP14SharedReligionGodSystem _god = default!;
[Dependency] private readonly SharedHandsSystem _hand = default!;
[Dependency] private readonly CP14SharedSkillSystem _skill = default!;
private void InitializeChecks()
{
@@ -26,6 +31,7 @@ public abstract partial class CP14SharedMagicSystem
SubscribeLocalEvent<CP14MagicEffectMaterialAspectComponent, CP14CastMagicEffectAttemptEvent>(OnMaterialCheck);
SubscribeLocalEvent<CP14MagicEffectManaCostComponent, CP14CastMagicEffectAttemptEvent>(OnManaCheck);
SubscribeLocalEvent<CP14MagicEffectStaminaCostComponent, CP14CastMagicEffectAttemptEvent>(OnStaminaCheck);
SubscribeLocalEvent<CP14MagicEffectSkillPointCostComponent, CP14CastMagicEffectAttemptEvent>(OnSkillPointCheck);
SubscribeLocalEvent<CP14MagicEffectPacifiedBlockComponent, CP14CastMagicEffectAttemptEvent>(OnPacifiedCheck);
SubscribeLocalEvent<CP14MagicEffectTargetMobStatusRequiredComponent, CP14CastMagicEffectAttemptEvent>(OnMobStateCheck);
SubscribeLocalEvent<CP14MagicEffectReligionRestrictedComponent, CP14CastMagicEffectAttemptEvent>(OnReligionRestrictedCheck);
@@ -33,9 +39,15 @@ public abstract partial class CP14SharedMagicSystem
//Verbal speaking
SubscribeLocalEvent<CP14MagicEffectVerbalAspectComponent, CP14StartCastMagicEffectEvent>(OnVerbalAspectStartCast);
SubscribeLocalEvent<CP14MagicEffectVerbalAspectComponent, CP14MagicEffectConsumeResourceEvent>(OnVerbalAspectAfterCast);
SubscribeLocalEvent<CP14MagicEffectEmotingComponent, CP14StartCastMagicEffectEvent>(OnEmoteStartCast);
SubscribeLocalEvent<CP14MagicEffectEmotingComponent, CP14MagicEffectConsumeResourceEvent>(OnEmoteEndCast);
//Consuming resources
SubscribeLocalEvent<CP14MagicEffectMaterialAspectComponent, CP14MagicEffectConsumeResourceEvent>(OnMaterialAspectEndCast);
SubscribeLocalEvent<CP14MagicEffectStaminaCostComponent, CP14MagicEffectConsumeResourceEvent>(OnStaminaConsume);
SubscribeLocalEvent<CP14MagicEffectManaCostComponent, CP14MagicEffectConsumeResourceEvent>(OnManaConsume);
SubscribeLocalEvent<CP14MagicEffectSkillPointCostComponent, CP14MagicEffectConsumeResourceEvent>(OnSkillPointConsume);
}
/// <summary>
@@ -85,6 +97,35 @@ public abstract partial class CP14SharedMagicSystem
args.Cancel();
}
private void OnSkillPointCheck(Entity<CP14MagicEffectSkillPointCostComponent> ent, ref CP14CastMagicEffectAttemptEvent args)
{
if (!_proto.TryIndex(ent.Comp.SkillPoint, out var indexedSkillPoint) || ent.Comp.SkillPoint is null)
return;
if (!TryComp<CP14SkillStorageComponent>(args.Performer, out var skillStorage))
{
args.PushReason(Loc.GetString("cp14-magic-spell-skillpoint-not-enough", ("name", Loc.GetString(indexedSkillPoint.Name)), ("count", ent.Comp.Count)));
args.Cancel();
return;
}
var points = skillStorage.SkillPoints;
if (points.TryGetValue(ent.Comp.SkillPoint.Value, out var currentPoints))
{
var freePoints = currentPoints.Max - currentPoints.Sum;
if (freePoints < ent.Comp.Count)
{
var d = ent.Comp.Count - freePoints;
args.PushReason(Loc.GetString("cp14-magic-spell-skillpoint-not-enough",
("name", Loc.GetString(indexedSkillPoint.Name)),
("count", d)));
args.Cancel();
}
}
}
private void OnSomaticCheck(Entity<CP14MagicEffectSomaticAspectComponent> ent,
ref CP14CastMagicEffectAttemptEvent args)
{
@@ -259,4 +300,43 @@ public abstract partial class CP14SharedMagicSystem
ent.Comp.Requirement.PostCraft(EntityManager, _proto, heldedItems);
}
private void OnStaminaConsume(Entity<CP14MagicEffectStaminaCostComponent> ent, ref CP14MagicEffectConsumeResourceEvent args)
{
if (args.Performer is null)
return;
_stamina.TakeStaminaDamage(args.Performer.Value, ent.Comp.Stamina, visual: false);
}
private void OnManaConsume(Entity<CP14MagicEffectManaCostComponent> ent, ref CP14MagicEffectConsumeResourceEvent args)
{
if (!TryComp<CP14MagicEffectComponent>(ent, out var magicEffect))
return;
var requiredMana = CalculateManacost(ent, args.Performer);
//First - used object
if (magicEffect.SpellStorage is not null && TryComp<CP14MagicEnergyContainerComponent>(magicEffect.SpellStorage, out var magicStorage))
{
var spellEv = new CP14SpellFromSpellStorageUsedEvent(args.Performer, (ent, magicEffect), requiredMana);
RaiseLocalEvent(magicEffect.SpellStorage.Value, ref spellEv);
_magicEnergy.ChangeEnergy((magicEffect.SpellStorage.Value, magicStorage), -requiredMana, out var changedEnergy, out var overloadedEnergy, safe: false);
requiredMana -= FixedPoint2.Abs(changedEnergy + overloadedEnergy);
}
//Second - action user
if (requiredMana > 0 &&
TryComp<CP14MagicEnergyContainerComponent>(args.Performer, out var playerMana))
_magicEnergy.ChangeEnergy((args.Performer.Value, playerMana), -requiredMana, out _, out _, safe: false);
}
private void OnSkillPointConsume(Entity<CP14MagicEffectSkillPointCostComponent> ent, ref CP14MagicEffectConsumeResourceEvent args)
{
if (!_proto.TryIndex(ent.Comp.SkillPoint, out var indexedSkillPoint) || ent.Comp.SkillPoint is null || args.Performer is null)
return;
_skill.RemoveSkillPoints(args.Performer.Value, ent.Comp.SkillPoint.Value, ent.Comp.Count);
}
}

View File

@@ -13,6 +13,7 @@ public abstract partial class CP14SharedMagicSystem
SubscribeLocalEvent<CP14MagicEffectManaCostComponent, ExaminedEvent>(OnManacostExamined);
SubscribeLocalEvent<CP14MagicEffectStaminaCostComponent, ExaminedEvent>(OnStaminaCostExamined);
SubscribeLocalEvent<CP14MagicEffectSkillPointCostComponent, ExaminedEvent>(OnSkillPointCostExamined);
SubscribeLocalEvent<CP14MagicEffectVerbalAspectComponent, ExaminedEvent>(OnVerbalExamined);
SubscribeLocalEvent<CP14MagicEffectSomaticAspectComponent, ExaminedEvent>(OnSomaticExamined);
@@ -39,6 +40,14 @@ public abstract partial class CP14SharedMagicSystem
args.PushMarkup($"{Loc.GetString("cp14-magic-staminacost")}: [color=#3fba54]{ent.Comp.Stamina}[/color]", priority: 9);
}
private void OnSkillPointCostExamined(Entity<CP14MagicEffectSkillPointCostComponent> ent, ref ExaminedEvent args)
{
if (!_proto.TryIndex(ent.Comp.SkillPoint, out var indexedSkillPoint))
return;
args.PushMarkup($"{Loc.GetString("cp14-magic-skillpointcost", ("name", Loc.GetString(indexedSkillPoint.Name)), ("count", ent.Comp.Count))}", priority: 9);
}
private void OnVerbalExamined(Entity<CP14MagicEffectVerbalAspectComponent> ent, ref ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("cp14-magic-verbal-aspect"), 8);

View File

@@ -58,8 +58,6 @@ public abstract partial class CP14SharedMagicSystem : EntitySystem
SubscribeLocalEvent<CP14MagicEffectComponent, CP14StartCastMagicEffectEvent>(OnStartCast);
SubscribeLocalEvent<CP14MagicEffectComponent, CP14EndCastMagicEffectEvent>(OnEndCast);
SubscribeLocalEvent<CP14MagicEffectStaminaCostComponent, CP14MagicEffectConsumeResourceEvent>(OnStaminaConsume);
}
private void OnStartCast(Entity<CP14MagicEffectComponent> ent, ref CP14StartCastMagicEffectEvent args)
@@ -127,6 +125,9 @@ public abstract partial class CP14SharedMagicSystem : EntitySystem
private void CastTelegraphy(Entity<CP14MagicEffectComponent> ent, CP14SpellEffectBaseArgs args)
{
if (!_timing.IsFirstTimePredicted)
return;
foreach (var effect in ent.Comp.TelegraphyEffects)
{
effect.Effect(EntityManager, args);
@@ -135,6 +136,9 @@ public abstract partial class CP14SharedMagicSystem : EntitySystem
private void CastSpell(Entity<CP14MagicEffectComponent> ent, CP14SpellEffectBaseArgs args)
{
if (!_timing.IsFirstTimePredicted)
return;
var ev = new CP14MagicEffectConsumeResourceEvent(args.User);
RaiseLocalEvent(ent, ref ev);
@@ -176,12 +180,4 @@ public abstract partial class CP14SharedMagicSystem : EntitySystem
return manaCost;
}
private void OnStaminaConsume(Entity<CP14MagicEffectStaminaCostComponent> ent, ref CP14MagicEffectConsumeResourceEvent args)
{
if (args.Performer is null)
return;
_stamina.TakeStaminaDamage(args.Performer.Value, ent.Comp.Stamina, visual: false);
}
}

View File

@@ -0,0 +1,18 @@
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Components;
/// <summary>
/// Restricts the use of this action, by spending user skillpoints
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedMagicSystem))]
public sealed partial class CP14MagicEffectSkillPointCostComponent : Component
{
[DataField(required: true)]
public ProtoId<CP14SkillPointPrototype>? SkillPoint;
[DataField]
public FixedPoint2 Count = 1f;
}

View File

@@ -1,6 +1,7 @@
using Content.Shared.Examine;
using Content.Shared.Popups;
using Robust.Shared.Map;
using Robust.Shared.Network;
namespace Content.Shared._CP14.MagicSpell.Spells;
@@ -11,6 +12,10 @@ public sealed partial class CP14SpellCasterTeleport : CP14SpellEffect
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
var net = IoCManager.Resolve<INetManager>();
if (net.IsClient)
return;
EntityCoordinates? targetPoint = null;
if (args.Position is not null)
targetPoint = args.Position.Value;

View File

@@ -1,5 +1,6 @@
using Content.Shared.Whitelist;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
@@ -20,6 +21,10 @@ public sealed partial class CP14SpellPointer : CP14SpellEffect
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
var net = IoCManager.Resolve<INetManager>();
if (net.IsClient)
return;
if (args.User is null)
return;

View File

@@ -1,6 +1,7 @@
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
@@ -15,6 +16,10 @@ public sealed partial class CP14SpellPointerToAlive : CP14SpellEffect
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
var net = IoCManager.Resolve<INetManager>();
if (net.IsClient)
return;
if (args.User is null)
return;

View File

@@ -19,6 +19,6 @@ public sealed partial class CP14SpellRemoveMemoryPoint : CP14SpellEffect
var skillSys = entManager.System<CP14SharedSkillSystem>();
skillSys.RemoveMemoryPoints(args.Target.Value, SkillPointType, RemovedPoints);
skillSys.RemoveSkillPoints(args.Target.Value, SkillPointType, RemovedPoints);
}
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
@@ -8,6 +9,9 @@ public sealed partial class CP14SpellSpawnEntityOnTarget : CP14SpellEffect
[DataField]
public List<EntProtoId> Spawns = new();
[DataField]
public bool Clientside = false;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
EntityCoordinates? targetPoint = null;
@@ -19,9 +23,21 @@ public sealed partial class CP14SpellSpawnEntityOnTarget : CP14SpellEffect
if (targetPoint is null)
return;
var netMan = IoCManager.Resolve<INetManager>();
foreach (var spawn in Spawns)
{
entManager.PredictedSpawnAtPosition(spawn, targetPoint.Value);
if (Clientside)
{
if (!netMan.IsClient)
continue;
entManager.SpawnAtPosition(spawn, targetPoint.Value);
}
else
{
entManager.PredictedSpawnAtPosition(spawn, targetPoint.Value);
}
}
}
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
@@ -8,14 +9,29 @@ public sealed partial class CP14SpellSpawnEntityOnUser : CP14SpellEffect
[DataField]
public List<EntProtoId> Spawns = new();
[DataField]
public bool Clientside = false;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.User is null || !entManager.TryGetComponent<TransformComponent>(args.User.Value, out var transformComponent))
return;
var netMan = IoCManager.Resolve<INetManager>();
foreach (var spawn in Spawns)
{
entManager.PredictedSpawnAtPosition(spawn, transformComponent.Coordinates);
if (Clientside)
{
if (!netMan.IsClient)
continue;
entManager.SpawnAtPosition(spawn, transformComponent.Coordinates);
}
else
{
entManager.PredictedSpawnAtPosition(spawn, transformComponent.Coordinates);
}
}
}
}

View File

@@ -21,7 +21,6 @@ public sealed partial class CP14SpellTeleportToCity : CP14SpellEffect
if (net.IsClient)
return;
var transform = entManager.System<SharedTransformSystem>();
var random = IoCManager.Resolve<IRobustRandom>();
var linkSys = entManager.System<LinkedEntitySystem>();
var query = entManager.EntityQueryEnumerator<CP14DemiplaneNavigationMapComponent, TransformComponent>();

View File

@@ -0,0 +1,46 @@
using System.Numerics;
using Content.Shared._CP14.UniqueLoot;
using Content.Shared.Teleportation.Systems;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellTeleportToSingleton : CP14SpellEffect
{
[DataField]
public EntProtoId PortalProto = "CP14TempPortalRed";
[DataField(required: true)]
public string SingletonKey;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Position is null)
return;
var net = IoCManager.Resolve<INetManager>();
if (net.IsClient)
return;
var random = IoCManager.Resolve<IRobustRandom>();
var linkSys = entManager.System<LinkedEntitySystem>();
var query = entManager.EntityQueryEnumerator<CP14SingletonComponent, TransformComponent>();
var first = entManager.SpawnAtPosition(PortalProto, args.Position.Value);
while (query.MoveNext(out var uid, out var singleton, out var xform))
{
if (singleton.Key != SingletonKey)
continue;
var randomOffset = new Vector2(random.Next(-1, 1), random.Next(-1, 1));
var second = entManager.SpawnAtPosition(PortalProto, xform.Coordinates.Offset(randomOffset));
linkSys.TryLink(first, second, true);
return;
}
}
}

View File

@@ -0,0 +1,67 @@
using Content.Shared._CP14.Vampire.Components;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.MagicSpell.Spells;
/// <summary>
/// Indicates all vampires within range belonging to the same faction as the caster. If inverted, it indicates enemy vampires.
/// </summary>
public sealed partial class CP14SpellPointerToVampireClan : CP14SpellEffect
{
[DataField(required: true)]
public EntProtoId PointerEntity;
[DataField(required: true)]
public float SearchRange = 60f;
[DataField]
public bool Inversed = false;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
var net = IoCManager.Resolve<INetManager>();
if (net.IsClient)
return;
if (args.User is null)
return;
if (!entManager.TryGetComponent<CP14VampireComponent>(args.User.Value, out var vampireComponent))
return;
var lookup = entManager.System<EntityLookupSystem>();
var transform = entManager.System<SharedTransformSystem>();
var originPosition = transform.GetWorldPosition(args.User.Value);
var originEntPosition = transform.GetMoverCoordinates(args.User.Value);
var entitiesInRange = lookup.GetEntitiesInRange<CP14VampireComponent>(originEntPosition, SearchRange);
foreach (var ent in entitiesInRange)
{
if (ent.Owner == args.User.Value)
continue;
if (!Inversed)
{
if (ent.Comp.Faction != vampireComponent.Faction)
continue;
}
else
{
if (ent.Comp.Faction == vampireComponent.Faction)
continue;
}
var targetPosition = transform.GetWorldPosition(ent);
//Calculate the rotation
Angle angle = new(targetPosition - originPosition);
var pointer = entManager.Spawn(PointerEntity, new MapCoordinates(originPosition, transform.GetMapId(originEntPosition)));
transform.SetWorldRotation(pointer, angle + Angle.FromDegrees(90));
}
}
}

View File

@@ -6,7 +6,7 @@ namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellSuckBlood : CP14SpellEffect
{
[DataField]
public FixedPoint2 SuckAmount = 25;
public FixedPoint2 SuckAmount = 10;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Target is null)

View File

@@ -0,0 +1,54 @@
using System.Numerics;
using Content.Shared._CP14.UniqueLoot;
using Content.Shared._CP14.Vampire.Components;
using Content.Shared.Teleportation.Systems;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellTeleportToVampireSingleton : CP14SpellEffect
{
[DataField]
public EntProtoId PortalProto = "CP14TempPortalRed";
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Position is null)
return;
if (args.User is null)
return;
if (!entManager.TryGetComponent<CP14VampireComponent>(args.User.Value, out var vampireComponent))
return;
var net = IoCManager.Resolve<INetManager>();
if (net.IsClient)
return;
var protoMan = IoCManager.Resolve<IPrototypeManager>();
var random = IoCManager.Resolve<IRobustRandom>();
var linkSys = entManager.System<LinkedEntitySystem>();
var query = entManager.EntityQueryEnumerator<CP14SingletonComponent, TransformComponent>();
if (!protoMan.TryIndex(vampireComponent.Faction, out var indexedVampireFaction))
return;
var first = entManager.SpawnAtPosition(PortalProto, args.Position.Value);
while (query.MoveNext(out var uid, out var singleton, out var xform))
{
if (singleton.Key != indexedVampireFaction.SingletonTeleportKey)
continue;
var randomOffset = new Vector2(random.Next(-1, 1), random.Next(-1, 1));
var second = entManager.SpawnAtPosition(PortalProto, xform.Coordinates.Offset(randomOffset));
linkSys.TryLink(first, second, true);
return;
}
}
}

View File

@@ -0,0 +1,29 @@
using Content.Shared._CP14.Vampire;
using Content.Shared._CP14.Vampire.Components;
using Content.Shared.FixedPoint;
namespace Content.Shared._CP14.MagicSpell.Spells;
public sealed partial class CP14SpellVampireGatherEssence : CP14SpellEffect
{
[DataField]
public FixedPoint2 Amount = 0.2f;
public override void Effect(EntityManager entManager, CP14SpellEffectBaseArgs args)
{
if (args.Target is null)
return;
if (args.User is null)
return;
if (entManager.HasComponent<CP14VampireComponent>(args.Target.Value))
return;
if (!entManager.TryGetComponent<CP14VampireEssenceHolderComponent>(args.Target.Value, out var essenceHolder))
return;
var vamp = entManager.System<CP14SharedVampireSystem>();
vamp.GatherEssence(args.User.Value, args.Target.Value, Amount);
}
}

View File

@@ -3,16 +3,16 @@ using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.NightVision;
[RegisterComponent, NetworkedComponent]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class CP14NightVisionComponent : Component
{
[DataField]
public EntityUid? LocalLightEntity = null;
[DataField]
[DataField, AutoNetworkedField]
public EntProtoId LightPrototype = "CP14NightVisionLight";
[DataField]
[DataField, AutoNetworkedField]
public EntProtoId ActionPrototype = "CP14ActionToggleNightVision";
[DataField]

View File

@@ -4,13 +4,25 @@ using Content.Shared._CP14.Skill.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared._CP14.Skill.Restrictions;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Stacks;
using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
namespace Content.Shared._CP14.Skill;
public abstract partial class CP14SharedSkillSystem : EntitySystem
{
[Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private EntityQuery<CP14SkillStorageComponent> _skillStorageQuery;
public override void Initialize()
@@ -20,12 +32,42 @@ public abstract partial class CP14SharedSkillSystem : EntitySystem
_skillStorageQuery = GetEntityQuery<CP14SkillStorageComponent>();
SubscribeLocalEvent<CP14SkillStorageComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<CP14SkillPointConsumableComponent, UseInHandEvent>(OnInteracted);
InitializeAdmin();
InitializeChecks();
InitializeScanning();
}
private void OnInteracted(Entity<CP14SkillPointConsumableComponent> ent, ref UseInHandEvent args)
{
if (!_timing.IsFirstTimePredicted)
return;
if (ent.Comp.Whitelist is null || !_whitelist.IsValid(ent.Comp.Whitelist, args.User))
return;
if (_net.IsServer)
{
var collect = ent.Comp.Volume;
if (TryComp<StackComponent>(ent, out var stack))
collect *= stack.Count;
AddSkillPoints(args.User, ent.Comp.PointType, collect);
}
var position = Transform(ent).Coordinates;
//Client VFX
if (_net.IsClient)
SpawnAtPosition(ent.Comp.ConsumeEffect, position);
_audio.PlayPredicted(ent.Comp.ConsumeSound, position, args.User);
PredictedQueueDel(ent.Owner);
}
private void OnMapInit(Entity<CP14SkillStorageComponent> ent, ref MapInitEvent args)
{
//If at initialization we have any skill records, we automatically give them to this entity
@@ -47,6 +89,31 @@ public abstract partial class CP14SharedSkillSystem : EntitySystem
}
}
/// <summary>
/// Adds a skill tree to the player, allowing them to learn skills from it.
/// </summary>
public void AddSkillTree(EntityUid target,
ProtoId<CP14SkillTreePrototype> tree,
CP14SkillStorageComponent? component = null)
{
if (!Resolve(target, ref component, false))
return;
component.AvailableSkillTrees.Add(tree);
DirtyField(target, component, nameof(CP14SkillStorageComponent.AvailableSkillTrees));
}
public void RemoveSkillTree(EntityUid target,
ProtoId<CP14SkillTreePrototype> tree,
CP14SkillStorageComponent? component = null)
{
if (!Resolve(target, ref component, false))
return;
component.AvailableSkillTrees.Remove(tree);
DirtyField(target, component, nameof(CP14SkillStorageComponent.AvailableSkillTrees));
}
/// <summary>
/// Directly adds the skill to the player, bypassing any checks.
/// </summary>
@@ -194,7 +261,7 @@ public abstract partial class CP14SharedSkillSystem : EntitySystem
//Restrictions check
foreach (var req in skill.Restrictions)
{
if (!req.Check(EntityManager, target, skill))
if (!req.Check(EntityManager, target))
return false;
}
@@ -231,9 +298,12 @@ public abstract partial class CP14SharedSkillSystem : EntitySystem
if (indexedSkill.Name is not null)
return Loc.GetString(indexedSkill.Name);
if (indexedSkill.Effects.Count > 0)
return indexedSkill.Effects.First().GetName(EntityManager, _proto) ?? string.Empty;
foreach (var effect in indexedSkill.Effects)
{
var name = effect.GetName(EntityManager, _proto);
if (name != null)
return name;
}
return string.Empty;
}
@@ -245,11 +315,11 @@ public abstract partial class CP14SharedSkillSystem : EntitySystem
if (!_proto.TryIndex(skill, out var indexedSkill))
return string.Empty;
if (indexedSkill.Desc is not null)
return Loc.GetString(indexedSkill.Desc);
var sb = new StringBuilder();
if (indexedSkill.Desc is not null)
sb.Append(Loc.GetString(indexedSkill.Desc));
foreach (var effect in indexedSkill.Effects)
{
sb.Append(effect.GetDescription(EntityManager, _proto, skill) + "\n");
@@ -297,9 +367,7 @@ public abstract partial class CP14SharedSkillSystem : EntitySystem
CP14SkillStorageComponent? component = null)
{
if (!Resolve(target, ref component, false))
{
return false;
}
for (var i = component.LearnedSkills.Count - 1; i >= 0; i--)
{
@@ -320,38 +388,61 @@ public abstract partial class CP14SharedSkillSystem : EntitySystem
public void AddSkillPoints(EntityUid target,
ProtoId<CP14SkillPointPrototype> type,
FixedPoint2 points,
FixedPoint2 limit,
FixedPoint2? limit = null,
bool silent = false,
CP14SkillStorageComponent? component = null)
{
if (points <= 0)
return;
if (!Resolve(target, ref component, false))
return;
if (component.SkillPoints.TryGetValue(type, out var skillContainer))
skillContainer.Max = FixedPoint2.Min(skillContainer.Max + points, limit);
if (!_proto.TryIndex(type, out var indexedType))
return;
Dirty(target, component);
if (!component.SkillPoints.TryGetValue(type, out var skillContainer))
{
skillContainer = new CP14SkillPointContainerEntry();
component.SkillPoints[type] = skillContainer;
}
_popup.PopupEntity(Loc.GetString("cp14-skill-popup-added-points", ("count", points)), target, target);
skillContainer.Max = limit is not null
? FixedPoint2.Min(skillContainer.Max + points, limit.Value)
: skillContainer.Max + points;
DirtyField(target, component, nameof(CP14SkillStorageComponent.SkillPoints));
if (indexedType.GetPointPopup is not null && !silent)
_popup.PopupPredicted(Loc.GetString(indexedType.GetPointPopup, ("count", points)), target, target);
}
/// <summary>
/// Removes memory points. If a character has accumulated skills exceeding the new memory limit, random skills will be removed.
/// </summary>
public void RemoveMemoryPoints(EntityUid target,
public void RemoveSkillPoints(EntityUid target,
ProtoId<CP14SkillPointPrototype> type,
FixedPoint2 points,
bool silent = false,
CP14SkillStorageComponent? component = null)
{
if (points <= 0)
return;
if (!Resolve(target, ref component, false))
return;
if (!_proto.TryIndex(type, out var indexedType))
return;
if (!component.SkillPoints.TryGetValue(type, out var skillContainer))
return;
skillContainer.Max = FixedPoint2.Max(skillContainer.Max - points, 0);
Dirty(target, component);
_popup.PopupEntity(Loc.GetString("cp14-skill-popup-removed-points", ("count", points)), target, target);
if (indexedType.LosePointPopup is not null && !silent)
_popup.PopupPredicted(Loc.GetString(indexedType.LosePointPopup, ("count", points)), target, target);
while (skillContainer.Sum > skillContainer.Max)
{

View File

@@ -14,7 +14,6 @@ namespace Content.Shared._CP14.Skill;
public abstract partial class CP14SharedSkillSystem
{
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;

View File

@@ -0,0 +1,42 @@
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.FixedPoint;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skill.Components;
/// <summary>
/// Allows you to see what skills the creature possesses
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class CP14SkillPointConsumableComponent : Component
{
[DataField, AutoNetworkedField]
public ProtoId<CP14SkillPointPrototype> PointType = "Memory";
/// <summary>
/// How much skill points this consumable gives when consumed.
/// </summary>
[DataField, AutoNetworkedField]
public FixedPoint2 Volume = 1f;
/// <summary>
/// The visual effect that appears on the client when the player consumes this skill point.
/// </summary>
[DataField]
public EntProtoId? ConsumeEffect;
[DataField]
public SoundSpecifier ConsumeSound = new SoundPathSpecifier("/Audio/_CP14/Effects/essence_consume.ogg")
{
Params = AudioParams.Default.WithVolume(-2f).WithVariation(0.2f),
};
/// <summary>
/// White list of who can absorb this skill point
/// </summary>
[DataField]
public EntityWhitelist? Whitelist;
}

View File

@@ -9,14 +9,14 @@ namespace Content.Shared._CP14.Skill.Components;
/// <summary>
/// Component that stores the skills learned by a player and their progress in the skill trees.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true, fieldDeltas: true)]
[Access(typeof(CP14SharedSkillSystem))]
public sealed partial class CP14SkillStorageComponent : Component
{
/// <summary>
/// Skill trees displayed in the skill tree interface. Only skills from these trees can be learned by this player.
/// </summary>
[DataField]
[DataField, AutoNetworkedField]
public HashSet<ProtoId<CP14SkillTreePrototype>> AvailableSkillTrees = new();
/// <summary>

View File

@@ -1,6 +1,8 @@
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.Actions;
using Content.Shared.Examine;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared._CP14.Skill.Effects;
@@ -65,6 +67,16 @@ public sealed partial class ReplaceAction : CP14SkillEffect
public override string? GetDescription(IEntityManager entMagager, IPrototypeManager protoManager, ProtoId<CP14SkillPrototype> skill)
{
return !protoManager.TryIndex(NewAction, out var indexedAction) ? string.Empty : indexedAction.Description;
var dummyAction = entMagager.Spawn(NewAction);
var message = new FormattedMessage();
if (!entMagager.TryGetComponent<MetaDataComponent>(dummyAction, out var meta))
return null;
message.AddText(meta.EntityDescription + "\n");
var ev = new ExaminedEvent(message, dummyAction, dummyAction, true, true);
entMagager.EventBus.RaiseLocalEvent(dummyAction, ev);
entMagager.DeleteEntity(dummyAction);
return ev.GetTotalMessage().ToMarkup();
}
}

View File

@@ -0,0 +1,101 @@
using System.Text;
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared._CP14.Skill.Restrictions;
using Content.Shared.Construction;
using Content.Shared.Construction.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skill.Effects;
/// <summary>
/// This effect only exists for parsing the description.
/// </summary>
public sealed partial class UnlockConstructions : CP14SkillEffect
{
public override void AddSkill(IEntityManager entManager, EntityUid target)
{
//
}
public override void RemoveSkill(IEntityManager entManager, EntityUid target)
{
//
}
public override string? GetName(IEntityManager entMagager, IPrototypeManager protoManager)
{
return null;
}
public override string? GetDescription(IEntityManager entMagager, IPrototypeManager protoManager, ProtoId<CP14SkillPrototype> skill)
{
var allRecipes = protoManager.EnumeratePrototypes<ConstructionPrototype>();
var sb = new StringBuilder();
sb.Append(Loc.GetString("cp14-skill-desc-unlock-constructions") + "\n");
var affectedRecipes = new List<ConstructionPrototype>();
foreach (var recipe in allRecipes)
{
foreach (var req in recipe.CP14Restrictions)
{
if (req is NeedPrerequisite prerequisite)
{
if (prerequisite.Prerequisite == skill)
{
affectedRecipes.Add(recipe);
break;
}
}
}
}
foreach (var constructionProto in affectedRecipes)
{
if (!protoManager.TryIndex(constructionProto.Graph, out var graphProto))
continue;
if (constructionProto.TargetNode is not { } targetNodeId)
continue;
if (!graphProto.Nodes.TryGetValue(targetNodeId, out var targetNode))
continue;
// Recursion is for wimps.
var stack = new Stack<ConstructionGraphNode>();
stack.Push(targetNode);
do
{
var node = stack.Pop();
// We try to get the id of the target prototype, if it fails, we try going through the edges.
if (node.Entity.GetId(null, null, new(entMagager)) is not { } entityId)
{
// If the stack is not empty, there is a high probability that the loop will go to infinity.
if (stack.Count == 0)
{
foreach (var edge in node.Edges)
{
if (graphProto.Nodes.TryGetValue(edge.Target, out var graphNode))
stack.Push(graphNode);
}
}
continue;
}
// If we got the id of the prototype, we exit the “recursion” by clearing the stack.
stack.Clear();
if (!protoManager.TryIndex(entityId, out var proto))
continue;
sb.Append("- " + Loc.GetString(proto.Name) + "\n");
} while (stack.Count > 0);
}
return sb.ToString();
}
}

View File

@@ -16,4 +16,10 @@ public sealed partial class CP14SkillPointPrototype : IPrototype
[DataField]
public SpriteSpecifier? Icon;
[DataField]
public LocId? GetPointPopup;
[DataField]
public LocId? LosePointPopup;
}

View File

@@ -8,7 +8,12 @@ namespace Content.Shared._CP14.Skill.Restrictions;
[MeansImplicitUse]
public abstract partial class CP14SkillRestriction
{
public abstract bool Check(IEntityManager entManager, EntityUid target, CP14SkillPrototype skill);
/// <summary>
/// If true - this skill won't be shown in skill tree if user doesn't meet this restriction
/// </summary>
public virtual bool HideFromUI => false;
public abstract bool Check(IEntityManager entManager, EntityUid target);
public abstract string GetDescription(IEntityManager entManager, IPrototypeManager protoManager);
}

View File

@@ -9,7 +9,7 @@ public sealed partial class GodFollowerPercentage : CP14SkillRestriction
{
[DataField]
public FixedPoint2 Percentage = 0.5f;
public override bool Check(IEntityManager entManager, EntityUid target, CP14SkillPrototype skill)
public override bool Check(IEntityManager entManager, EntityUid target)
{
if (!entManager.TryGetComponent<CP14ReligionEntityComponent>(target, out var god))
return false;

View File

@@ -1,12 +1,10 @@
using Content.Shared._CP14.Skill.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skill.Restrictions;
public sealed partial class Impossible : CP14SkillRestriction
{
public override bool Check(IEntityManager entManager, EntityUid target, CP14SkillPrototype skill)
public override bool Check(IEntityManager entManager, EntityUid target)
{
return false;
}

View File

@@ -9,7 +9,7 @@ public sealed partial class NeedPrerequisite : CP14SkillRestriction
[DataField(required: true)]
public ProtoId<CP14SkillPrototype> Prerequisite = new();
public override bool Check(IEntityManager entManager, EntityUid target, CP14SkillPrototype skill)
public override bool Check(IEntityManager entManager, EntityUid target)
{
if (!entManager.TryGetComponent<CP14SkillStorageComponent>(target, out var skillStorage))
return false;

View File

@@ -7,10 +7,12 @@ namespace Content.Shared._CP14.Skill.Restrictions;
public sealed partial class SpeciesBlacklist : CP14SkillRestriction
{
public override bool HideFromUI => true;
[DataField(required: true)]
public ProtoId<SpeciesPrototype> Species = new();
public override bool Check(IEntityManager entManager, EntityUid target, CP14SkillPrototype skill)
public override bool Check(IEntityManager entManager, EntityUid target)
{
if (!entManager.TryGetComponent<HumanoidAppearanceComponent>(target, out var appearance))
return false;

View File

@@ -1,4 +1,3 @@
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Robust.Shared.Prototypes;
@@ -7,10 +6,12 @@ namespace Content.Shared._CP14.Skill.Restrictions;
public sealed partial class SpeciesWhitelist : CP14SkillRestriction
{
public override bool HideFromUI => true;
[DataField(required: true)]
public ProtoId<SpeciesPrototype> Species = new();
public override bool Check(IEntityManager entManager, EntityUid target, CP14SkillPrototype skill)
public override bool Check(IEntityManager entManager, EntityUid target)
{
if (!entManager.TryGetComponent<HumanoidAppearanceComponent>(target, out var appearance))
return false;

View File

@@ -0,0 +1,40 @@
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.CCVar;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Shared._CP14.Skill.Restrictions;
public sealed partial class TimeGate : CP14SkillRestriction
{
[DataField(required: true)]
public int Minutes = 1;
public override bool Check(IEntityManager entManager, EntityUid target)
{
var timing = IoCManager.Resolve<IGameTiming>();
var cfg = IoCManager.Resolve<IConfigurationManager>();
if (cfg.GetCVar(CCVars.CP14SkillTimers) == false)
return true;
return timing.CurTime >= TimeSpan.FromMinutes(Minutes);
}
public override string GetDescription(IEntityManager entManager, IPrototypeManager protoManager)
{
var timing = IoCManager.Resolve<IGameTiming>();
var cfg = IoCManager.Resolve<IConfigurationManager>();
var leftoverTime = TimeSpan.FromMinutes(Minutes) - timing.CurTime;
leftoverTime = leftoverTime < TimeSpan.Zero ? TimeSpan.Zero : leftoverTime;
if (cfg.GetCVar(CCVars.CP14SkillTimers) == false)
return Loc.GetString("cp14-skill-req-timegate-disabled", ("minute", Minutes));
return Loc.GetString("cp14-skill-req-timegate", ("minute", Minutes), ("left", Math.Ceiling(leftoverTime.TotalMinutes)));
}
}

View File

@@ -0,0 +1,45 @@
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared._CP14.Vampire.Components;
using Content.Shared.CCVar;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Shared._CP14.Skill.Restrictions;
public sealed partial class VampireClanLevel : CP14SkillRestriction
{
[DataField]
public int Level = 1;
public override bool Check(IEntityManager entManager, EntityUid target)
{
if (!entManager.TryGetComponent<CP14VampireComponent>(target, out var playerVampire))
return false;
if (!entManager.TryGetComponent<TransformComponent>(target, out var xform))
return false;
var lookup = entManager.System<EntityLookupSystem>();
foreach (var tree in lookup.GetEntitiesInRange<CP14VampireClanHeartComponent>(xform.Coordinates, 2))
{
if (tree.Comp.Faction != playerVampire.Faction)
continue;
if (tree.Comp.Level < Level)
continue;
return true;
}
return false;
}
public override string GetDescription(IEntityManager entManager, IPrototypeManager protoManager)
{
return Loc.GetString("cp14-skill-req-vampire-tree-level", ("lvl", Level));
}
}

View File

@@ -0,0 +1,28 @@
using Content.Shared._CP14.Vampire;
using Content.Shared._CP14.Vampire.Components;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Skill.Restrictions;
public sealed partial class VampireFaction : CP14SkillRestriction
{
public override bool HideFromUI => true;
[DataField(required: true)]
public ProtoId<CP14VampireFactionPrototype> Clan;
public override bool Check(IEntityManager entManager, EntityUid target)
{
if (!entManager.TryGetComponent<CP14VampireComponent>(target, out var vampire))
return false;
return vampire.Faction == Clan;
}
public override string GetDescription(IEntityManager entManager, IPrototypeManager protoManager)
{
var clan = protoManager.Index(Clan);
return Loc.GetString("cp14-skill-req-vampire-clan", ("name", Loc.GetString(clan.Name)));
}
}

View File

@@ -0,0 +1,11 @@
namespace Content.Shared._CP14.UniqueLoot;
/// <summary>
/// The appearance of an entity with this component will remove all other entities with the same component and key inside, ensuring the uniqueness of the entity.
/// </summary>
[RegisterComponent]
public sealed partial class CP14SingletonComponent : Component
{
[DataField(required: true)]
public string Key = string.Empty;
}

View File

@@ -0,0 +1,38 @@
using Content.Shared._CP14.MagicSpell.Events;
using Content.Shared._CP14.Vampire.Components;
using Content.Shared.Examine;
using Content.Shared.Mobs.Systems;
using Content.Shared.SSDIndicator;
namespace Content.Shared._CP14.Vampire;
public abstract partial class CP14SharedVampireSystem
{
[Dependency] private readonly MobStateSystem _mobState = default!;
private void InitializeSpell()
{
SubscribeLocalEvent<CP14MagicEffectVampireComponent, CP14CastMagicEffectAttemptEvent>(OnVampireCastAttempt);
SubscribeLocalEvent<CP14MagicEffectVampireComponent, ExaminedEvent>(OnVampireCastExamine);
}
private void OnVampireCastAttempt(Entity<CP14MagicEffectVampireComponent> ent, ref CP14CastMagicEffectAttemptEvent args)
{
//If we are not vampires in principle, we certainly should not have this ability,
//but then we will not limit its use to a valid vampire form that is unavailable to us.
if (!HasComp<CP14VampireComponent>(args.Performer))
return;
if (!HasComp<CP14VampireVisualsComponent>(args.Performer))
{
args.PushReason(Loc.GetString("cp14-magic-spell-need-vampire-valid"));
args.Cancel();
}
}
private void OnVampireCastExamine(Entity<CP14MagicEffectVampireComponent> ent, ref ExaminedEvent args)
{
args.PushMarkup($"{Loc.GetString("cp14-magic-spell-need-vampire-valid")}");
}
}

View File

@@ -0,0 +1,218 @@
using Content.Shared._CP14.Skill;
using Content.Shared._CP14.Skill.Components;
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared._CP14.Vampire.Components;
using Content.Shared.Actions;
using Content.Shared.Body.Systems;
using Content.Shared.Buckle.Components;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
using Content.Shared.Jittering;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
namespace Content.Shared._CP14.Vampire;
public abstract partial class CP14SharedVampireSystem : EntitySystem
{
[Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!;
[Dependency] private readonly SharedActionsSystem _action = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedJitteringSystem _jitter = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly CP14SharedSkillSystem _skill = default!;
[Dependency] protected readonly IPrototypeManager Proto = default!;
private readonly ProtoId<CP14SkillPointPrototype> _skillPointType = "Blood";
private readonly ProtoId<CP14SkillPointPrototype> _memorySkillPointType = "Memory";
public override void Initialize()
{
base.Initialize();
InitializeSpell();
SubscribeLocalEvent<CP14VampireComponent, MapInitEvent>(OnVampireInit);
SubscribeLocalEvent<CP14VampireComponent, ComponentRemove>(OnVampireRemove);
SubscribeLocalEvent<CP14VampireComponent, CP14ToggleVampireVisualsAction>(OnToggleVisuals);
SubscribeLocalEvent<CP14VampireComponent, CP14VampireToggleVisualsDoAfter>(OnToggleDoAfter);
SubscribeLocalEvent<CP14VampireVisualsComponent, ComponentInit>(OnVampireVisualsInit);
SubscribeLocalEvent<CP14VampireVisualsComponent, ComponentShutdown>(OnVampireVisualsShutdown);
SubscribeLocalEvent<CP14VampireVisualsComponent, ExaminedEvent>(OnVampireExamine);
SubscribeLocalEvent<CP14VampireEssenceHolderComponent, ExaminedEvent>(OnEssenceHolderExamined);
}
private void OnEssenceHolderExamined(Entity<CP14VampireEssenceHolderComponent> ent, ref ExaminedEvent args)
{
if (!HasComp<CP14ShowVampireEssenceComponent>(args.Examiner))
return;
if (!args.IsInDetailsRange)
return;
args.PushMarkup(Loc.GetString("cp14-vampire-essence-holder-examine", ("essence", ent.Comp.Essence)));
}
protected virtual void OnVampireInit(Entity<CP14VampireComponent> ent, ref MapInitEvent args)
{
//Bloodstream
_bloodstream.ChangeBloodReagent(ent.Owner, ent.Comp.NewBloodReagent);
//Actions
foreach (var proto in ent.Comp.ActionsProto)
{
EntityUid? newAction = null;
_action.AddAction(ent, ref newAction, proto);
}
//Skill tree
_skill.AddSkillPoints(ent, ent.Comp.SkillPointProto, ent.Comp.SkillPointCount, silent: true);
_skill.AddSkillTree(ent, ent.Comp.SkillTreeProto);
//Skill tree base nerf
_skill.RemoveSkillPoints(ent, _memorySkillPointType, 2, true);
//Remove blood essence
if (TryComp<CP14VampireEssenceHolderComponent>(ent, out var essenceHolder))
{
essenceHolder.Essence = 0;
Dirty(ent, essenceHolder);
}
}
private void OnVampireRemove(Entity<CP14VampireComponent> ent, ref ComponentRemove args)
{
RemCompDeferred<CP14VampireVisualsComponent>(ent);
//Bloodstream todo
//Metabolism todo
//Actions
foreach (var action in ent.Comp.Actions)
{
_action.RemoveAction(ent.Owner, action);
}
//Skill tree
_skill.RemoveSkillTree(ent, ent.Comp.SkillTreeProto);
if (TryComp<CP14SkillStorageComponent>(ent, out var storage))
{
foreach (var skill in storage.LearnedSkills)
{
if (!Proto.TryIndex(skill, out var indexedSkill))
continue;
if (indexedSkill.Tree == ent.Comp.SkillTreeProto)
_skill.TryRemoveSkill(ent, skill);
}
}
_skill.RemoveSkillPoints(ent, ent.Comp.SkillPointProto, ent.Comp.SkillPointCount);
_skill.AddSkillPoints(ent, _memorySkillPointType, 2, null, true);
}
private void OnToggleVisuals(Entity<CP14VampireComponent> ent, ref CP14ToggleVampireVisualsAction args)
{
if (_timing.IsFirstTimePredicted)
_jitter.DoJitter(ent, ent.Comp.ToggleVisualsTime, true);
var doAfterArgs = new DoAfterArgs(EntityManager, ent, ent.Comp.ToggleVisualsTime, new CP14VampireToggleVisualsDoAfter(), ent)
{
Hidden = true,
NeedHand = false,
};
_doAfter.TryStartDoAfter(doAfterArgs);
}
private void OnToggleDoAfter(Entity<CP14VampireComponent> ent, ref CP14VampireToggleVisualsDoAfter args)
{
if (args.Cancelled || args.Handled)
return;
if (HasComp<CP14VampireVisualsComponent>(ent))
{
RemCompDeferred<CP14VampireVisualsComponent>(ent);
}
else
{
EnsureComp<CP14VampireVisualsComponent>(ent);
}
args.Handled = true;
}
protected virtual void OnVampireVisualsShutdown(Entity<CP14VampireVisualsComponent> vampire, ref ComponentShutdown args)
{
if (!EntityManager.TryGetComponent(vampire, out HumanoidAppearanceComponent? humanoidAppearance))
return;
humanoidAppearance.EyeColor = vampire.Comp.OriginalEyesColor;
Dirty(vampire, humanoidAppearance);
}
protected virtual void OnVampireVisualsInit(Entity<CP14VampireVisualsComponent> vampire, ref ComponentInit args)
{
if (!EntityManager.TryGetComponent(vampire, out HumanoidAppearanceComponent? humanoidAppearance))
return;
vampire.Comp.OriginalEyesColor = humanoidAppearance.EyeColor;
humanoidAppearance.EyeColor = vampire.Comp.EyesColor;
Dirty(vampire, humanoidAppearance);
}
private void OnVampireExamine(Entity<CP14VampireVisualsComponent> ent, ref ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("cp14-vampire-examine"));
}
public void GatherEssence(Entity<CP14VampireComponent?> vampire,
Entity<CP14VampireEssenceHolderComponent?> victim,
FixedPoint2 amount)
{
if (!Resolve(vampire, ref vampire.Comp, false))
return;
if (!Resolve(victim, ref victim.Comp, false))
return;
var extractedEssence = MathF.Min(victim.Comp.Essence.Float(), amount.Float());
if (TryComp<BuckleComponent>(victim, out var buckle) && buckle.BuckledTo is not null)
{
if (TryComp<CP14VampireAltarComponent>(buckle.BuckledTo, out var altar))
{
extractedEssence *= altar.Multiplier;
}
}
if (extractedEssence <= 0)
return;
_skill.AddSkillPoints(vampire, _skillPointType, extractedEssence);
victim.Comp.Essence -= amount;
Dirty(victim);
}
}
public sealed partial class CP14ToggleVampireVisualsAction : InstantActionEvent;
[Serializable, NetSerializable]
public sealed partial class CP14VampireToggleVisualsDoAfter : SimpleDoAfterEvent;
// Appearance Data key
[Serializable, NetSerializable]
public enum VampireClanLevelVisuals : byte
{
Level,
}

View File

@@ -1,43 +0,0 @@
using Content.Shared.Examine;
using Content.Shared.Humanoid;
namespace Content.Shared._CP14.Vampire;
public abstract class CP14SharedVampireVisualsSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CP14VampireVisualsComponent, ExaminedEvent>(OnVampireExamine);
SubscribeLocalEvent<CP14VampireVisualsComponent, ComponentInit>(OnVampireVisualsInit);
SubscribeLocalEvent<CP14VampireVisualsComponent, ComponentShutdown>(OnVampireVisualsShutdown);
}
protected virtual void OnVampireVisualsShutdown(Entity<CP14VampireVisualsComponent> vampire, ref ComponentShutdown args)
{
if (!EntityManager.TryGetComponent(vampire, out HumanoidAppearanceComponent? humanoidAppearance))
return;
humanoidAppearance.EyeColor = vampire.Comp.OriginalEyesColor;
Dirty(vampire, humanoidAppearance);
}
protected virtual void OnVampireVisualsInit(Entity<CP14VampireVisualsComponent> vampire, ref ComponentInit args)
{
if (!EntityManager.TryGetComponent(vampire, out HumanoidAppearanceComponent? humanoidAppearance))
return;
vampire.Comp.OriginalEyesColor = humanoidAppearance.EyeColor;
humanoidAppearance.EyeColor = vampire.Comp.EyesColor;
Dirty(vampire, humanoidAppearance);
}
private void OnVampireExamine(Entity<CP14VampireVisualsComponent> ent, ref ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("cp14-vampire-examine"));
}
}

View File

@@ -0,0 +1,19 @@
using Content.Shared.StatusIcon;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Vampire;
[Prototype("cp14VampireFaction")]
public sealed partial class CP14VampireFactionPrototype : IPrototype
{
[IdDataField] public string ID { get; private set; } = default!;
[DataField(required: true)]
public LocId Name = string.Empty;
[DataField(required: true)]
public ProtoId<FactionIconPrototype> FactionIcon;
[DataField(required: true)]
public string SingletonTeleportKey = string.Empty;
}

View File

@@ -0,0 +1,11 @@
using Content.Shared._CP14.MagicSpell;
namespace Content.Shared._CP14.Vampire.Components;
/// <summary>
/// Use is only available if the vampire is in a “visible” dangerous form.
/// </summary>
[RegisterComponent, Access(typeof(CP14SharedVampireSystem))]
public sealed partial class CP14MagicEffectVampireComponent : Component
{
}

View File

@@ -0,0 +1,9 @@
using Robust.Shared.GameStates;
namespace Content.Shared._CP14.Vampire.Components;
[RegisterComponent]
[NetworkedComponent]
public sealed partial class CP14ShowVampireEssenceComponent : Component
{
}

View File

@@ -0,0 +1,15 @@
using Content.Shared.StatusIcon;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Vampire.Components;
[RegisterComponent]
[NetworkedComponent]
[AutoGenerateComponentState]
[Access(typeof(CP14SharedVampireSystem))]
public sealed partial class CP14ShowVampireFactionComponent : Component
{
[DataField, AutoNetworkedField]
public ProtoId<CP14VampireFactionPrototype>? Faction;
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameStates;
namespace Content.Shared._CP14.Vampire.Components;
/// <summary>
/// increases the amount of blood essence extracted if the victim is strapped to the altar
/// </summary>
[RegisterComponent]
[NetworkedComponent]
[AutoGenerateComponentState]
[Access(typeof(CP14SharedVampireSystem))]
public sealed partial class CP14VampireAltarComponent : Component
{
[DataField, AutoNetworkedField]
public float Multiplier = 2f;
}

View File

@@ -0,0 +1,85 @@
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Vampire.Components;
[RegisterComponent]
[NetworkedComponent]
[AutoGenerateComponentState]
[Access(typeof(CP14SharedVampireSystem))]
public sealed partial class CP14VampireClanHeartComponent : Component
{
[DataField, AutoNetworkedField]
public FixedPoint2 CollectedEssence = 0f;
[DataField]
public string LevelPrefix = "orb";
[DataField]
public EntProtoId LevelUpVfx = "CP14SkyLightningRed";
[DataField]
public ProtoId<CP14VampireFactionPrototype>? Faction;
[DataField]
public FixedPoint2 Level2 = 5f;
[DataField]
public FixedPoint2 Level3 = 12f;
[DataField]
public FixedPoint2 Level4 = 21f;
[DataField]
public FixedPoint2 EssenceRegenPerLevel = 0.1f;
[DataField]
public TimeSpan RegenFrequency = TimeSpan.FromMinutes(1);
[DataField]
public TimeSpan NextRegenTime = TimeSpan.Zero;
/// <summary>
/// For reduce damage announce spamming
/// </summary>
[DataField]
public TimeSpan MaxAnnounceFreq = TimeSpan.FromSeconds(10f);
/// <summary>
/// For reduce damage announce spamming
/// </summary>
[DataField]
public TimeSpan NextAnnounceTime = TimeSpan.Zero;
public int Level
{
get
{
if (CollectedEssence >= Level4)
return 4;
if (CollectedEssence >= Level3)
return 3;
if (CollectedEssence >= Level2)
return 2;
return 1;
}
}
public FixedPoint2 EssenceFromLevelStart => Level switch
{
1 => CollectedEssence,
2 => CollectedEssence - Level2,
3 => CollectedEssence - Level3,
4 => CollectedEssence - Level4,
_ => FixedPoint2.Zero
};
public FixedPoint2? EssenceToNextLevel => Level switch
{
1 => Level2,
2 => Level3 - Level2,
3 => Level4 - Level3,
_ => null
};
}

View File

@@ -0,0 +1,61 @@
using Content.Shared._CP14.Skill.Prototypes;
using Content.Shared.Body.Prototypes;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Vampire.Components;
[RegisterComponent]
[NetworkedComponent]
[AutoGenerateComponentState]
[Access(typeof(CP14SharedVampireSystem))]
public sealed partial class CP14VampireComponent : Component
{
[DataField]
public ProtoId<ReagentPrototype> NewBloodReagent = "CP14BloodVampire";
[DataField]
public ProtoId<CP14SkillTreePrototype> SkillTreeProto = "Vampire";
[DataField]
public ProtoId<MetabolizerTypePrototype> MetabolizerType = "CP14Vampire";
[DataField]
public ProtoId<CP14SkillPointPrototype> SkillPointProto = "Blood";
[DataField(required: true), AutoNetworkedField]
public ProtoId<CP14VampireFactionPrototype>? Faction;
[DataField]
public FixedPoint2 SkillPointCount = 1f;
[DataField]
public TimeSpan ToggleVisualsTime = TimeSpan.FromSeconds(2f);
/// <summary>
/// All this actions was granted to vampires on component added
/// </summary>
[DataField]
public List<EntProtoId> ActionsProto = new() { "CP14ActionVampireToggleVisuals" };
/// <summary>
/// For tracking granted actions, and removing them when component is removed.
/// </summary>
[DataField]
public List<EntityUid> Actions = new();
[DataField]
public float HeatUnderSunTemperature = 12000f;
[DataField]
public TimeSpan HeatFrequency = TimeSpan.FromSeconds(1);
[DataField]
public TimeSpan NextHeatTime = TimeSpan.Zero;
[DataField]
public float IgniteThreshold = 350f;
public override bool SendOnlyToOwner => true;
}

View File

@@ -0,0 +1,14 @@
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
namespace Content.Shared._CP14.Vampire.Components;
[RegisterComponent]
[NetworkedComponent]
[AutoGenerateComponentState]
[Access(typeof(CP14SharedVampireSystem))]
public sealed partial class CP14VampireEssenceHolderComponent : Component
{
[DataField, AutoNetworkedField]
public FixedPoint2 Essence = 1f;
}

View File

@@ -0,0 +1,17 @@
using Content.Shared.FixedPoint;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
namespace Content.Shared._CP14.Vampire.Components;
[RegisterComponent]
[NetworkedComponent]
[Access(typeof(CP14SharedVampireSystem))]
public sealed partial class CP14VampireTreeCollectableComponent : Component
{
[DataField]
public FixedPoint2 Essence = 1f;
[DataField]
public SoundSpecifier CollectSound = new SoundPathSpecifier("/Audio/_CP14/Effects/essence_consume.ogg");
}

View File

@@ -1,4 +1,5 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._CP14.Vampire;
@@ -13,4 +14,10 @@ public sealed partial class CP14VampireVisualsComponent : Component
[DataField]
public string FangsMap = "vampire_fangs";
[DataField]
public EntProtoId EnableVFX = "CP14ImpactEffectBloodEssence2";
[DataField]
public EntProtoId DisableVFX = "CP14ImpactEffectBloodEssenceInverse";
}

View File

@@ -1,4 +1,9 @@
- files: ["bandit_start.ogg"]
license: "CC-BY-4.0"
copyright: 'by Victor_Natas of Freesound.org'
source: "https://freesound.org/people/Victor_Natas/sounds/612156/"
source: "https://freesound.org/people/Victor_Natas/sounds/612156/"
- files: ["vampire.ogg"]
license: "CC-BY-NC-3.0"
copyright: 'by SergeQuadrado of Freesound.org.'
source: "https://freesound.org/people/SergeQuadrado/sounds/455364/"

Binary file not shown.

View File

@@ -6,4 +6,9 @@
- files: ["darkness_boom.ogg", "darkness_boom_2.ogg"]
license: "CC-BY-4.0"
copyright: 'by Uzbazur of Freesound.org.'
source: "https://freesound.org/people/Uzbazur/sounds/442241/"
source: "https://freesound.org/people/Uzbazur/sounds/442241/"
- files: ["vampire.ogg"]
license: "CC0-1.0"
copyright: 'by MathewHenry of Freesound.org.'
source: "https://freesound.org/people/MathewHenry/sounds/636196/"

Binary file not shown.

View File

@@ -68,10 +68,10 @@
copyright: 'by DustyWind on Freesound.org'
source: "https://freesound.org/people/DustyWind/sounds/715784/"
- files: ["vampire_bite.ogg"]
license: "CC0-1.0"
copyright: 'by magnuswaker on Freesound.org'
source: "https://freesound.org/people/magnuswaker/sounds/563491/"
- files: ["vampire_craft.ogg"]
license: "CC-BY-4.0"
copyright: 'by Victor_Natas on Freesound.org'
source: "https://freesound.org/people/Victor_Natas/sounds/616217/"
- files: ["skill_up1.ogg", "skill_up2.ogg", "skill_up3.ogg", "skill_up3.ogg"]
license: "CC0-1.0"
@@ -116,4 +116,9 @@
- files: ["pan_open.ogg", "pan_close.ogg"]
license: "CC0-1.0"
copyright: 'Created by RossBell on Freesound.org'
source: "https://freesound.org/people/RossBell/sounds/389428/"
source: "https://freesound.org/people/RossBell/sounds/389428/"
- files: ["surprise.ogg"]
license: "CC0-1.0"
copyright: 'Created by qubodup on Freesound.org'
source: "https://freesound.org/people/qubodup/sounds/814055/"

Binary file not shown.

Binary file not shown.

View File

@@ -12,3 +12,8 @@
license: "CC-BY-SA-3.0"
copyright: "'Arcane Winds' by LINK"
source: "https://github.com/crystallpunk-14/crystall-punk-14"
- files: ["blood_fog.ogg"]
license: "CC-BY-4.0"
copyright: "'Creeping Blood Fog' by SoundFlakes"
source: "https://freesound.org/people/SoundFlakes/sounds/751524/"

Binary file not shown.

View File

@@ -45,3 +45,9 @@ ssd_sleep_time = 3600
[server]
rules_file = "CP14Rules"
[audio]
lobby_music_collection = "CP14LobbyMusic"
[cp14]
skill_timers = false

View File

@@ -21,7 +21,7 @@ log_late_msg = false
hostname = "⚔️ CrystallEdge ⚔️ [MRP]"
desc = "History of the City of Sword and Magic. A social economic sandbox reinventing the Space Station 14 concept in fantasy style"
lobbyenabled = true
soft_max_players = 40
soft_max_players = 50
maxplayers = 80
lobbyduration = 300
role_timers = true

View File

@@ -1,6 +1,10 @@
cp14-roles-antag-vampire-name = Vampire
cp14-roles-antag-vampire-objective = You are a parasite on the body of society, hated by those around you, burned by the sun, and eternally hungry. You need to feed on the blood of the sentient to survive. And finding those who will volunteer to be your feeder is not easy...
cp14-roles-antag-vampire-briefing = You are a parasite on society. It hates and fears you, but the blood of the living is your only food. Nature destroys you with sunlight, so you have to hide in the shadows. It's like the whole world is trying to destroy you, but your will to live is stronger than all of that. SURVIVE. That's all you have to do.
cp14-roles-antag-vampire-briefing-night-childrens = As a representative of the newest clan of Children of the Night, you claim rights to this city. Working from the shadows and without revealing your presence to ordinary mortals, find and destroy other clans, proving your superiority.
cp14-roles-antag-vampire-briefing-unnameable = As a representative of the oldest clan of the Unnameables, you claim rights to this city. Working from the shadows and without revealing your presence to ordinary mortals, find and destroy other clans, proving your superiority.
cp14-roles-antag-vampire-briefing-devourers = As a member of the ancient clan of Devourers, you claim this city as your own. Working from the shadows and keeping your presence hidden from ordinary mortals, find and destroy the other clans, proving your superiority.
cp14-roles-antag-vampire-objective = As a representative of one of the oldest vampire clans, you claim rights to this city. Working from the shadows and without revealing your presence to ordinary mortals, find and destroy other clans, proving your superiority.
cp14-roles-antag-blood-moon-cursed-name = Cursed by the blood moon
cp14-roles-antag-blood-moon-cursed-objective = Some creatures lose their minds, and can commit unthinkable atrocities when a bloody blood moon rises from behind the horizon ...

View File

@@ -1 +1,2 @@
cp14-construction-condition-mana-filled = The structure must be fully powered by mana.
cp14-construction-condition-mana-filled = The structure must be fully powered by mana.
cp14-construction-condition-singleton = Can only exist in a single copy!

View File

@@ -0,0 +1,3 @@
cp14-contraband-examine-text-CP14Minor = [color=yellow]Ownership of this item is considered a Mild violation.[/color]
cp14-contraband-examine-text-CP14Major = [color=red]Ownership of this item is considered a Serious violation.[/color]

View File

@@ -1,2 +1,5 @@
cp14-gamemode-survival-title = Survival
cp14-gamemode-survival-description = Your ship is in distress and crashing into wild, dangerous lands. What will you do to survive? Which of you will find the way back to civilization? And who... or what is watching you from the darkness...
cp14-gamemode-survival-description = Your ship is in distress and crashing into wild, dangerous lands. What will you do to survive? Which of you will find the way back to civilization? And who... or what is watching you from the darkness...
cp14-peaceful-title = Peaceful time
cp14-peaceful-description = A peaceful existence without major problems. Find something you enjoy doing.

View File

@@ -0,0 +1,19 @@
cp14-vampire-clans-battle = Battle of the Vampire Clans
cp14-vampire-clans-description = Several vampire clans are laying claim to the city. Only one of them will become the true ruler of the land...
cp14-vampire-clans-battle-clan-win = Victory for the "{$name}" clan!
cp14-vampire-clans-battle-clan-win-desc = The vampire clan, having proven its power, becomes the secret ruler of these lands.
cp14-vampire-clans-battle-clan-tie-2 = A draw between the clans "{$name1}" and "{$name2}"
cp14-vampire-clans-battle-clan-tie-2-desc = Two clans, unable to defeat each other, are forced to divide these lands between themselves.
cp14-vampire-clans-battle-clan-tie-3 = A draw between all clans
cp14-vampire-clans-battle-clan-tie-3-desc = Vampire clans that have failed to defeat each other are forced to divide these lands among themselves.
cp14-vampire-clans-battle-clan-city-win = Victory of the settlement
cp14-vampire-clans-battle-clan-city-win-desc = All vampire clans have been exterminated, and residents can sleep safely.
cp14-vampire-clans-battle-clan-lose = Total defeat
cp14-vampire-clans-battle-clan-lose-desc = Most of the settlement was destroyed in the fighting between the clans. Even the surviving clans can no longer feed themselves on these lands.
cp14-vampire-clans-battle-alive-people = Percentage of surviving population: [color=red]{$percent}%[/color]

View File

@@ -29,4 +29,7 @@ cp14-magic-spell-target-mob-state-dead = dead
cp14-magic-spell-target-mob-state-live = living
cp14-magic-spell-target-mob-state-critical = dying
cp14-magic-spell-target-god-follower = Your target should be your follower!
cp14-magic-spell-target-god-follower = Your target should be your follower!
cp14-magic-skillpointcost = Resource costs "{$name}": [color=#eba834]{$count}[/color]
cp14-magic-spell-skillpoint-not-enough = There are {$count} resources of "{$name}" missing!

View File

@@ -0,0 +1,7 @@
cp14-objective-issuer-vampire = [color="#c20034"]Vampire clan[/color]
cp14-objective-vampire-pure-bood-title = Expel foreign vampire clans
cp14-objective-vampire-pure-bood-desc = Intruders from other vampire clans are hiding among the residents. Eliminate them so that the settlement belongs only to you.
cp14-objective-vampire-defence-settlement-title = Keep your property
cp14-objective-vampire-defence-settlement-desc = The inhabitants of this city are your property and your food. Don't let them die. At least {$count}% of the inhabitants must survive.

View File

@@ -1,3 +0,0 @@
cp14-steal-target-dino = yumkaraptors
cp14-steal-target-mole = predatory moles
cp14-steal-target-boar = boars or pigs

View File

@@ -1,6 +1,10 @@
cp14-skill-req-prerequisite = Skill "{$name}" must be learned
cp14-skill-req-species = You must be the race of “{$name}”
cp14-skill-req-notspecies = You must not be the race of “{$name}”
cp14-skill-req-vampire-clan = You must belong to the vampire clan "{$name}"
cp14-skill-req-researched = A study needs to be done on the research table
cp14-skill-req-impossible = Unable to explore during a round at the current moment
cp14-skill-req-god-follower-percentage = The number of your followers should be more than {$count}%
cp14-skill-req-god-follower-percentage = The number of your followers should be more than {$count}%
cp14-skill-req-timegate = Available for study {$minute} minutes after the start of the round. Minutes remaining: {$left}
cp14-skill-req-timegate-disabled = Available for study {$minute} minutes after the start of the round, but time restrictions are disabled.
cp14-skill-req-vampire-tree-level = You must be near the heart of your clan, at least {$lvl} level.

View File

@@ -42,4 +42,14 @@ cp14-skill-mithril-melt-name = Mithril melting
cp14-skill-glass-melt-name = Glasswork
cp14-skill-trader-wit-name = Trader's wit
cp14-skill-trader-wit-desc = You are able to estimate the exact value of any item in the empire at a first glance.
cp14-skill-trader-wit-desc = You are able to estimate the exact value of any item in the empire at a first glance.
# Vampire
cp14-skill-vampire-night-vision-name = Night vision
cp14-skill-vampire-night-vision-desc = Darkness cannot be an obstacle for a creature of the night.
cp14-skill-vampire-essence-vision-name = Analysis of blood
cp14-skill-vampire-essence-vision-desc = You are able to see how much essence can be extracted from the surrounding creatures.
cp14-skill-vampire-transmutate-name = Blood transmutation

View File

@@ -1,2 +1,2 @@
cp14-skill-point-memory = Memory
cp14-skill-point-vampire-blood = Vampiric powers
cp14-skill-point-vampire-blood = Blood essence

View File

@@ -42,4 +42,9 @@ cp14-skill-tree-martial-desc = Master the secrets of deadly weapons, or make you
#cp14-skill-tree-trading-desc = The art of understanding where, when and for how much to sell and buy different items.
cp14-skill-tree-craftsman-name = Craftsmanship
cp14-skill-tree-craftsman-desc = Learn the arts and crafts that let you create and use all kinds of useful things.
cp14-skill-tree-craftsman-desc = Learn the arts and crafts that let you create and use all kinds of useful things.
# Vampires
cp14-skill-tree-vampire-name = Vampire powers
cp14-skill-tree-vampire-desc = Vampire clan. Ask Wandederere to write the lore here.

View File

@@ -11,8 +11,12 @@ cp14-skill-menu-free = [color=green]This skill is innate to your character, and
cp14-skill-desc-add-mana = Increases your character's mana amount by {$mana}.
cp14-skill-desc-add-stamina = Increases your character's stamina amount by {$stamina}.
cp14-skill-desc-unlock-recipes = Opens up the possibility of crafting:
cp14-skill-desc-unlock-constructions = Opens up the possibility of structure crafting:
cp14-skill-popup-added-points = The boundaries of your consciousness are expanding. New memory points: {$count}
cp14-skill-popup-memory-added = The boundaries of your consciousness are expanding. New memory points: {$count}
cp14-skill-popup-memory-losed = You are beginning to forget your past... Memory points lost: {$count}
cp14-skill-examine-title = This character has the following skills:
cp14-skill-popup-forced-remove-skill = You are beginning to forget your past... Memory points lost: {$count}
cp14-skill-popup-blood-added = You absorb the life force of others. Blood essences obtained: {$count}
cp14-skill-popup-blood-losed = You are losing life force. Blood essence lost: {$count}
cp14-skill-examine-title = This character has the following skills:

Some files were not shown because too many files have changed in this diff Show More