Merge remote-tracking branch 'upstream/master' into funnyfunnystuff
This commit is contained in:
@@ -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)))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,6 +284,8 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
|
||||
}
|
||||
}
|
||||
|
||||
if (!hide)
|
||||
{
|
||||
var nodeTreeElement = new CP14NodeTreeElement(
|
||||
skill.ID,
|
||||
gained: learned.Contains(skill),
|
||||
@@ -287,6 +294,7 @@ public sealed class CP14SkillUIController : UIController, IOnStateEntered<Gamepl
|
||||
skill.Icon);
|
||||
nodeTreeElements.Add(nodeTreeElement);
|
||||
}
|
||||
}
|
||||
|
||||
_window.GraphControl.UpdateState(
|
||||
new CP14NodeTreeUiState(
|
||||
|
||||
39
Content.Client/_CP14/Vampire/CP14ClientVampireSystem.cs
Normal file
39
Content.Client/_CP14/Vampire/CP14ClientVampireSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
46
Content.Client/_CP14/Vampire/CP14ShowVampireIconsSystem.cs
Normal file
46
Content.Client/_CP14/Vampire/CP14ShowVampireIconsSystem.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Overlays;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,7 @@ public sealed partial class CP14DemiplaneSystem : CP14SharedDemiplaneSystem
|
||||
}
|
||||
|
||||
stationMap.GeneratedNodes.Add(targetPosition.Value);
|
||||
_map.CreateMap(out var mapId, runMapInit: false);
|
||||
_map.CreateMap(out var mapId, runMapInit: true);
|
||||
var mapUid = _map.GetMap(mapId);
|
||||
EnsureComp<CP14DemiplaneMapComponent>(mapUid).Position = targetPosition.Value;
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed class CP14ExpeditionToWindlandsRule : GameRuleSystem<CP14Expeditio
|
||||
|
||||
EnsureComp<ShuttleComponent>(largestStationGrid.Value, out var shuttleComp);
|
||||
|
||||
var windlands = _mapSystem.CreateMap(out var mapId, runMapInit: false);
|
||||
var windlands = _mapSystem.CreateMap(out var mapId, runMapInit: true);
|
||||
|
||||
_generation.GenerateLocation(windlands, mapId, component.Location, component.Modifiers);
|
||||
_shuttles.FTLToCoordinates(largestStationGrid.Value, shuttleComp, new EntityCoordinates(windlands, Vector2.Zero), 0f, 0f, component.FloatingTime);
|
||||
|
||||
@@ -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();
|
||||
|
||||
var aliveFactions = new HashSet<ProtoId<CP14VampireFactionPrototype>>();
|
||||
|
||||
var query = EntityQueryEnumerator<CP14VampireComponent, MobStateComponent>();
|
||||
while (query.MoveNext(out var vampireUid, out var vampire, out var mobState))
|
||||
{
|
||||
if (mobState.CurrentState != MobState.Alive)
|
||||
continue;
|
||||
|
||||
if (vampire.Faction is null)
|
||||
continue;
|
||||
|
||||
aliveFactions.Add(vampire.Faction.Value);
|
||||
}
|
||||
|
||||
private void OnVampireHungerChanged(Entity<CP14VampireComponent> ent, ref CP14HungerChangedEvent args)
|
||||
args.AddLine($"[head=2][color=#ab1b3d]{Loc.GetString("cp14-vampire-clans-battle")}[/color][/head]");
|
||||
|
||||
if (alivePercentage > _condition.RequiredAlivePercentage)
|
||||
{
|
||||
if (args.NewThreshold == HungerThreshold.Starving || args.NewThreshold == HungerThreshold.Dead)
|
||||
if (aliveFactions.Count == 0)
|
||||
{
|
||||
RevealVampire(ent);
|
||||
//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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -80,8 +80,6 @@ public sealed class CP14SpawnProceduralLocationJob(
|
||||
var mixture = new GasMixture(moles, Atmospherics.T20C);
|
||||
entManager.System<AtmosphereSystem>().SetMapAtmosphere(MapUid, false, mixture);
|
||||
|
||||
if (!map.IsInitialized(mapId))
|
||||
map.InitializeMap(mapId);
|
||||
map.SetPaused(mapId, false);
|
||||
|
||||
//Spawn modified config
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
102
Content.Server/_CP14/Vampire/CP14VampireSystem.Announce.cs
Normal file
102
Content.Server/_CP14/Vampire/CP14VampireSystem.Announce.cs
Normal 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"));
|
||||
}
|
||||
}
|
||||
176
Content.Server/_CP14/Vampire/CP14VampireSystem.cs
Normal file
176
Content.Server/_CP14/Vampire/CP14VampireSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
using Content.Shared._CP14.Vampire;
|
||||
|
||||
namespace Content.Server._CP14.Vampire;
|
||||
|
||||
public sealed class CP14VampireVisualsSystem : CP14SharedVampireVisualsSystem
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,12 @@ public sealed class CP14DayCycleSystem : EntitySystem
|
||||
return (float)lightLevel;
|
||||
}
|
||||
|
||||
public bool IsDayNow(Entity<LightCycleComponent?> map)
|
||||
{
|
||||
var lightLevel = GetLightLevel(map);
|
||||
return lightLevel >= 0.5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the specified entity is on the map where it's daytime.
|
||||
/// </summary>
|
||||
@@ -101,7 +107,7 @@ public sealed class CP14DayCycleSystem : EntitySystem
|
||||
if (xform.MapUid is null || xform.GridUid is null)
|
||||
return false;
|
||||
|
||||
var day = GetLightLevel(xform.MapUid.Value) > 0.5f;
|
||||
var day = IsDayNow(xform.MapUid.Value);
|
||||
|
||||
var grid = xform.GridUid;
|
||||
if (grid is null)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
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;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Speech.Muting;
|
||||
using Content.Shared.SSDIndicator;
|
||||
|
||||
namespace Content.Shared._CP14.MagicSpell;
|
||||
|
||||
@@ -18,6 +23,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,16 +32,24 @@ 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<CP14MagicEffectSSDBlockComponent, CP14CastMagicEffectAttemptEvent>(OnSSDCheck);
|
||||
SubscribeLocalEvent<CP14MagicEffectTargetMobStatusRequiredComponent, CP14CastMagicEffectAttemptEvent>(OnMobStateCheck);
|
||||
SubscribeLocalEvent<CP14MagicEffectReligionRestrictedComponent, CP14CastMagicEffectAttemptEvent>(OnReligionRestrictedCheck);
|
||||
|
||||
//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 +99,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)
|
||||
{
|
||||
@@ -139,6 +182,21 @@ public abstract partial class CP14SharedMagicSystem
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnSSDCheck(Entity<CP14MagicEffectSSDBlockComponent> ent, ref CP14CastMagicEffectAttemptEvent args)
|
||||
{
|
||||
if (args.Target is null)
|
||||
return;
|
||||
|
||||
if (!TryComp<SSDIndicatorComponent>(args.Target.Value, out var ssdIndication))
|
||||
return;
|
||||
|
||||
if (ssdIndication.IsSSD)
|
||||
{
|
||||
args.PushReason(Loc.GetString("cp14-magic-spell-ssd"));
|
||||
args.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMobStateCheck(Entity<CP14MagicEffectTargetMobStatusRequiredComponent> ent,
|
||||
ref CP14CastMagicEffectAttemptEvent args)
|
||||
{
|
||||
@@ -259,4 +317,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Content.Shared._CP14.MagicSpell.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Blocks the target from using magic if they are pacified.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(CP14SharedMagicSystem))]
|
||||
public sealed partial class CP14MagicEffectSSDBlockComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
if (Clientside)
|
||||
{
|
||||
if (!netMan.IsClient)
|
||||
continue;
|
||||
|
||||
entManager.SpawnAtPosition(spawn, targetPoint.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
entManager.PredictedSpawnAtPosition(spawn, targetPoint.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
if (Clientside)
|
||||
{
|
||||
if (!netMan.IsClient)
|
||||
continue;
|
||||
|
||||
entManager.SpawnAtPosition(spawn, transformComponent.Coordinates);
|
||||
}
|
||||
else
|
||||
{
|
||||
entManager.PredictedSpawnAtPosition(spawn, transformComponent.Coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -18,8 +18,8 @@ public sealed partial class CP14IsNight : RulesRule
|
||||
if (map is null)
|
||||
return false;
|
||||
|
||||
var lightLevel = dayCycle.GetLightLevel(map.Value);
|
||||
var isDay = dayCycle.IsDayNow(map.Value);
|
||||
|
||||
return Inverted ? lightLevel < 0.5 : lightLevel >= 0.5;
|
||||
return Inverted ? !isDay : isDay;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 && _timing.IsFirstTimePredicted)
|
||||
_popup.PopupClient(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 && _timing.IsFirstTimePredicted)
|
||||
_popup.PopupClient(Loc.GetString(indexedType.LosePointPopup, ("count", points)), target, target);
|
||||
|
||||
while (skillContainer.Sum > skillContainer.Max)
|
||||
{
|
||||
|
||||
@@ -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!;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
101
Content.Shared/_CP14/Skill/Effects/UnlockConstructions.cs
Normal file
101
Content.Shared/_CP14/Skill/Effects/UnlockConstructions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,10 @@ public sealed partial class CP14SkillPointPrototype : IPrototype
|
||||
|
||||
[DataField]
|
||||
public SpriteSpecifier? Icon;
|
||||
|
||||
[DataField]
|
||||
public LocId? GetPointPopup;
|
||||
|
||||
[DataField]
|
||||
public LocId? LosePointPopup;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
29
Content.Shared/_CP14/Skill/Restrictions/SpeciesBlacklist.cs
Normal file
29
Content.Shared/_CP14/Skill/Restrictions/SpeciesBlacklist.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Content.Shared._CP14.Skill.Prototypes;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
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)
|
||||
{
|
||||
if (!entManager.TryGetComponent<HumanoidAppearanceComponent>(target, out var appearance))
|
||||
return false;
|
||||
|
||||
return appearance.Species != Species;
|
||||
}
|
||||
|
||||
public override string GetDescription(IEntityManager entManager, IPrototypeManager protoManager)
|
||||
{
|
||||
var species = protoManager.Index(Species);
|
||||
|
||||
return Loc.GetString("cp14-skill-req-notspecies", ("name", Loc.GetString(species.Name)));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
40
Content.Shared/_CP14/Skill/Restrictions/TimeGate.cs
Normal file
40
Content.Shared/_CP14/Skill/Restrictions/TimeGate.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
45
Content.Shared/_CP14/Skill/Restrictions/VampireClanLevel.cs
Normal file
45
Content.Shared/_CP14/Skill/Restrictions/VampireClanLevel.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
28
Content.Shared/_CP14/Skill/Restrictions/VampireFaction.cs
Normal file
28
Content.Shared/_CP14/Skill/Restrictions/VampireFaction.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
11
Content.Shared/_CP14/UniqueLoot/CP14SingletonComponent.cs
Normal file
11
Content.Shared/_CP14/UniqueLoot/CP14SingletonComponent.cs
Normal 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;
|
||||
}
|
||||
@@ -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")}");
|
||||
}
|
||||
}
|
||||
223
Content.Shared/_CP14/Vampire/CP14SharedVampireSystem.cs
Normal file
223
Content.Shared/_CP14/Vampire/CP14SharedVampireSystem.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
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 Content.Shared.Popups;
|
||||
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!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = 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)
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("cp14-vampire-gather-essence-no-left"), victim, vampire, PopupType.SmallCaution);
|
||||
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,
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
19
Content.Shared/_CP14/Vampire/CP14VampireFactionPrototype.cs
Normal file
19
Content.Shared/_CP14/Vampire/CP14VampireFactionPrototype.cs
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared._CP14.Vampire.Components;
|
||||
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent]
|
||||
public sealed partial class CP14ShowVampireEssenceComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
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 = 2f;
|
||||
|
||||
[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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -2,3 +2,8 @@
|
||||
license: "CC-BY-4.0"
|
||||
copyright: 'by Victor_Natas of Freesound.org'
|
||||
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/"
|
||||
BIN
Resources/Audio/_CP14/Ambience/Antag/vampire.ogg
Normal file
BIN
Resources/Audio/_CP14/Ambience/Antag/vampire.ogg
Normal file
Binary file not shown.
@@ -7,3 +7,8 @@
|
||||
license: "CC-BY-4.0"
|
||||
copyright: 'by Uzbazur of Freesound.org.'
|
||||
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/"
|
||||
BIN
Resources/Audio/_CP14/Announce/vampire.ogg
Normal file
BIN
Resources/Audio/_CP14/Announce/vampire.ogg
Normal file
Binary file not shown.
@@ -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"
|
||||
@@ -117,3 +117,8 @@
|
||||
license: "CC0-1.0"
|
||||
copyright: 'Created by RossBell on Freesound.org'
|
||||
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/"
|
||||
BIN
Resources/Audio/_CP14/Effects/surprise.ogg
Normal file
BIN
Resources/Audio/_CP14/Effects/surprise.ogg
Normal file
Binary file not shown.
Binary file not shown.
BIN
Resources/Audio/_CP14/Effects/vampire_craft.ogg
Normal file
BIN
Resources/Audio/_CP14/Effects/vampire_craft.ogg
Normal file
Binary file not shown.
@@ -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/"
|
||||
|
||||
BIN
Resources/Audio/_CP14/Lobby/blood_fog.ogg
Normal file
BIN
Resources/Audio/_CP14/Lobby/blood_fog.ogg
Normal file
Binary file not shown.
@@ -1654,3 +1654,110 @@
|
||||
id: 8217
|
||||
time: '2025-08-11T20:46:28.0000000+00:00'
|
||||
url: https://github.com/crystallpunk-14/crystall-punk-14/pull/1661
|
||||
- author: KittyCat432
|
||||
changes:
|
||||
- message: Changed Ice shard to now do 2.5 pierce and 5 cold total of 7.5 damage.
|
||||
type: Tweak
|
||||
- message: Changed Fire wave to now have a 0.5 second cast time that cannot be interrupted
|
||||
and nolonger adjusts the temperature inside of someone.
|
||||
type: Tweak
|
||||
- message: Changed magic, speed, and hell ballade spells to nolonger break upon
|
||||
being damaged.
|
||||
type: Tweak
|
||||
- message: Changed Hell ballade now takes 8 mana per second and slows you down by
|
||||
50% speed.
|
||||
type: Tweak
|
||||
- message: Fixed hell ballad, heat, and freeze nolonger work while under pacifism.
|
||||
type: Fix
|
||||
id: 8218
|
||||
time: '2025-08-16T16:44:33.0000000+00:00'
|
||||
url: https://github.com/crystallpunk-14/crystall-punk-14/pull/1671
|
||||
- author: Nimfar11
|
||||
changes:
|
||||
- message: Added price for artefacts bottomless goblet and magic healing staff.
|
||||
type: Add
|
||||
id: 8219
|
||||
time: '2025-08-17T22:09:29.0000000+00:00'
|
||||
url: https://github.com/crystallpunk-14/crystall-punk-14/pull/1674
|
||||
- author: Nimfar11
|
||||
changes:
|
||||
- message: Adds the book Pantheon of Gods of Sileita, with a description of the
|
||||
gods. And an additional book about the imitators of gods.
|
||||
type: Add
|
||||
id: 8220
|
||||
time: '2025-08-17T22:10:17.0000000+00:00'
|
||||
url: https://github.com/crystallpunk-14/crystall-punk-14/pull/1673
|
||||
- author: Viator-MV
|
||||
changes:
|
||||
- message: The bone masks were nerfed
|
||||
type: Tweak
|
||||
- message: Skeletons' health has been increased to 100
|
||||
type: Tweak
|
||||
- message: Now raider and skeleton wizard chooses his own spells.
|
||||
type: Tweak
|
||||
id: 8221
|
||||
time: '2025-08-17T22:19:02.0000000+00:00'
|
||||
url: https://github.com/crystallpunk-14/crystall-punk-14/pull/1447
|
||||
- author: KittyCat432
|
||||
changes:
|
||||
- message: Added the Cackle a ranged monster exclusive to places with open skys
|
||||
and not cold weather.
|
||||
type: Add
|
||||
- message: Added a new monster toxin called hemoroxide which causes bleeding and
|
||||
piercing damage, used by the Cackle.
|
||||
type: Add
|
||||
id: 8222
|
||||
time: '2025-08-18T08:41:34.0000000+00:00'
|
||||
url: https://github.com/crystallpunk-14/crystall-punk-14/pull/1602
|
||||
- author: Morb0
|
||||
changes:
|
||||
- message: Silvas and Elfs clothing now always displayed as female
|
||||
type: Fix
|
||||
id: 8223
|
||||
time: '2025-08-22T15:02:14.0000000+00:00'
|
||||
url: https://github.com/crystallpunk-14/crystall-punk-14/pull/1691
|
||||
- author: TheShuEd
|
||||
changes:
|
||||
- message: "A new game mode, \u201CVampire Clan Battle\u201D has been added. Three\
|
||||
\ ancient vampire clans battle each other for control of the settlement."
|
||||
type: Add
|
||||
id: 8224
|
||||
time: '2025-08-22T15:46:28.0000000+00:00'
|
||||
url: https://github.com/crystallpunk-14/crystall-punk-14/pull/1672
|
||||
- author: TheShuEd
|
||||
changes:
|
||||
- message: Fixed a bug where vampires could not see other vampires
|
||||
type: Fix
|
||||
- message: "removed tier 1 skill \u201CDemonstration of power\u201D for vampires\
|
||||
\ (only t2 left)"
|
||||
type: Remove
|
||||
- message: "Added a new vampire tier 1 skill \u201CHypnosis\u201D - allows you to\
|
||||
\ keep the target asleep for up to 30 seconds."
|
||||
type: Add
|
||||
- message: Starting blood essence supply for vampires increased from 1 to 2
|
||||
type: Add
|
||||
- message: All vampire buildings in the construction menu on G are now in a separate
|
||||
category
|
||||
type: Fix
|
||||
- message: Starting vampire skills are now cheaper
|
||||
type: Tweak
|
||||
- message: Vampire hunger has been significantly reduced
|
||||
type: Fix
|
||||
- message: Rabbits and boars now spawn around game maps instead of aggressive mobs
|
||||
type: Tweak
|
||||
- message: Vampires can no longer bite SSD players!
|
||||
type: Tweak
|
||||
- message: Vampire blood can no longer be identified without alchemical vision.
|
||||
type: Fix
|
||||
- message: The tieflings blood no longer ignites vampires!
|
||||
type: Remove
|
||||
id: 8225
|
||||
time: '2025-08-22T22:56:01.0000000+00:00'
|
||||
url: https://github.com/crystallpunk-14/crystall-punk-14/pull/1696
|
||||
- author: Viator-MV
|
||||
changes:
|
||||
- message: The lightning strike has become stronger. Proceed using it!
|
||||
type: Tweak
|
||||
id: 8226
|
||||
time: '2025-08-23T09:00:27.0000000+00:00'
|
||||
url: https://github.com/crystallpunk-14/crystall-punk-14/pull/1695
|
||||
|
||||
@@ -45,3 +45,9 @@ ssd_sleep_time = 3600
|
||||
|
||||
[server]
|
||||
rules_file = "CP14Rules"
|
||||
|
||||
[audio]
|
||||
lobby_music_collection = "CP14LobbyMusic"
|
||||
|
||||
[cp14]
|
||||
skill_timers = false
|
||||
@@ -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
|
||||
|
||||
@@ -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 ...
|
||||
|
||||
141
Resources/Locale/en-US/_CP14/books/pantheon_gods_sileita.ftl
Normal file
141
Resources/Locale/en-US/_CP14/books/pantheon_gods_sileita.ftl
Normal file
@@ -0,0 +1,141 @@
|
||||
cp14-book-text-pantheon-gods-sileita = The pantheon of Sileita is divided into three great ranks: [bold]Absolutes, Elder Gods and Younger Gods[/bold]. Faith in the gods affects the political, spiritual and magical life of all the peoples of Sileita. Some deities are forbidden to worship, others are revered as official patrons of countries or organizations.
|
||||
{"[italic]'I am a Watcher, a templar of the Order of the Guardians of Truth, charged with gathering information on all the gods of Sileita without bias. We do not pray. We record. No god is worthy of worship until fully understood. None is superior to another. All are part of a complex picture.'[/italic]"}
|
||||
|
||||
{"[head=2]Absolutes — First Forces[/head]"}
|
||||
|
||||
{"[head=3][color=purple]Tomer[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Mutable, Primordial Contradiction, Sculpting. Rejected.[/italic]
|
||||
• [bold]Idea[/bold]: Chaos, metamorphosis, biological and metaphysical evolution.
|
||||
• [bold]Symbol[/bold]: Spiral of creeping forms.
|
||||
• [bold]Presence[/bold]: Physical appearance is imprisoned in a divine prison. His "comings" are catastrophes: mutations, bursts of alien biomes, falls of flesh from the sky. Tomer's influence is flashes of his energy capable of changing the environment around him.
|
||||
• [bold]Character[/bold]: He cannot be called evil — he is beyond the concepts of good. This is blind curiosity in destruction. He changes for the sake of change itself. The mind of the pulsating world, which dreams of becoming something new.
|
||||
• [bold]Attitude to mortals[/bold]: He is interested in them as material. He is not an executioner, but a craftsman. Dispassionate, but attentive.
|
||||
• [bold]Comment[/bold]: It is dangerous not so much because of its strength, but because it infects minds with the idea of destruction as a method of progress. Its prohibition by law is justified. Its teaching is a sweet poison.
|
||||
|
||||
{"[head=3][color=purple]Altaran[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Flickering Judgement, Course of Currents, Watcher Through the Ages.[/italic]
|
||||
• [bold]Idea[/bold]: Time, temporal currents, judgement, order.
|
||||
• [bold]Symbol[/bold]: Eternal circle with internal clocks running in different directions.
|
||||
• [bold]Presence[/bold]: Never appears in the flesh. Never seen in Sileith, but has been seen in the Void.
|
||||
• [bold]Character[/bold]: Absolutely impartial. No anger in him, only equation. Disgusting to mortals because he does not forgive - but does not take revenge either. He is the unspoken leader of the entire pantheon and the controlling authority.
|
||||
• [bold]Attitude to mortals[/bold]: Sees them as units in a model. Does not deny the importance of personality, but only recognizes its influence on the course of time.
|
||||
• [bold]Comment[/bold]: If all the gods disappeared, Altaran would remain to watch their work crumble.
|
||||
|
||||
{"[head=3][color=purple]Relaphir and Vestris[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Twins of the Passage, Key and Lock, Primordial Gate. Wolf and Sheep.[/italic]
|
||||
• [bold]Idea[/bold]: Life, death, soul, reincarnation, Void.
|
||||
• [bold]Symbol[/bold]: Intertwined circles with an eye in the center.
|
||||
• [bold]Presence[/bold]: At the moment of birth - Vestris watches. At the moment of death - Relaphir sees off. In deep silence and in the last breath - both. Only Relaphir physically appeared on Bafamir's judgment day.
|
||||
• [bold]Character[/bold]:
|
||||
- Vestris is soft and thoughtful, like a spring morning.
|
||||
- Relaphir is reserved and inevitable, like the night.
|
||||
They do not argue. [bold]Their unity is fundamental.[/bold]
|
||||
• [bold]Attitude to mortals[/bold]: They do not judge, do not encourage, do not persuade. They simply are. Accompany, without interfering.
|
||||
• [bold]Comment[/bold]: They are not terrible because of their power, but because they have the right to use it. Necromancers and experienced healers can accept the faith of the twins for the sake of studying resurrection and rebirth.
|
||||
|
||||
{"[head=2]Elder Gods - Firstborn[/head]"}
|
||||
|
||||
{"[head=3][color=forestgreen]Irumel[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Fallen Shield, Faithful, Last of the Stars.[/italic]
|
||||
• [bold]Idea[/bold]: Protection, honor, self-sacrifice.
|
||||
• [bold]Symbol[/bold]: A broken shield emitting light.
|
||||
• [bold]Presence[/bold]: [bold]Believed to have died[/bold] in the divine war. But in the ruins of the temples, his presence is felt as a call to duty. It is said that a wounded silhouette can be seen in the former places of his faith.
|
||||
• [bold]Character[/bold]: He was the embodiment of service. They said he was silent for hours, but with a single glance he convinced the army.
|
||||
• [bold]Attitude to mortals[/bold]: He saw in them, perhaps, more than they deserved. Believed that they were worth protecting at any cost.
|
||||
• [bold]Comment[/bold]: If he is dead, then in that case - the gods are mortal. This is disturbing.
|
||||
|
||||
{"[head=3][color=forestgreen]Devias / Fenor[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Silver Shadow, Whisper in Dreams, Listener to the Unnamed.[/italic]
|
||||
• [bold]Idea[/bold]: Dreams, secrets, foresight, concealment.
|
||||
• [bold]Symbol[/bold]: Eye, vertically closed with a finger.
|
||||
• [bold]Presence[/bold]: Almost never physically appears, but through rituals one can briefly talk to him, but the price will be high.
|
||||
• [bold]Character[/bold]: Quiet and friendly, but anxious. Speaks in hints. Possesses monstrous knowledge.
|
||||
• [bold]Attitude to mortals[/bold]: Warns, but does not interfere. He who listens is saved. He who demands is driven mad.
|
||||
• [bold]Comment[/bold]: Not an enemy. But not an ally either.
|
||||
|
||||
{"[head=3][color=forestgreen]Gelarion[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Black Flame, Bloody Teacher, Echo of Resentment.[/italic]
|
||||
• [bold]Idea[/bold]: Revenge, rage, retribution, anger.
|
||||
• [bold]Symbol[/bold]: A flaming heart pierced by a dagger.
|
||||
• [bold]Presence[/bold]: Comes at the moment of the oath of vengeance. Often not directly, but through visions or hallucinations at the moment of the oath.
|
||||
• [bold]Character[/bold]: Burningly bright, with a predatory charisma.
|
||||
• [bold]Attitude to mortals[/bold]: Loves their pain, but not for the sake of suffering.
|
||||
• [bold]Comment[/bold]: He is dangerous not because he gives strength, but because he makes the victim sweet.
|
||||
|
||||
{"[head=3][color=forestgreen]Merkas[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Embalming Light, Merciful Brother, He who Cleanses.[/italic]
|
||||
• [bold]Idea[/bold]: Healing, purification, resurrection.
|
||||
• [bold]Symbol[/bold]: White palm, driving away darkness.
|
||||
• [bold]Presence[/bold]: Appears in miracles: when the mortal disappears without a trace, when a curse is broken.
|
||||
• [bold]Character[/bold]: Calm. Does not judge, but does not forget. Speaks softly, acts precisely.
|
||||
• [bold]Attitude to mortals[/bold]: Sees in them an opportunity for restoration.
|
||||
• [bold]Comment[/bold]: Of all, he is closest to what is called "good". But he is not without a shadow. Purification can become an obsession.
|
||||
|
||||
{"[head=2]Lesser Gods — Ascended or Second Scions[/head]"}
|
||||
|
||||
{"[head=3][color=blue]Rikhiard / Helmir[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Iron Gaze, Patron of Duels, Eternal Commander.[/italic]
|
||||
• [bold]Idea[/bold]: War, battle, honor.
|
||||
• [bold]Symbol[/bold]: Crossed swords above a helmet.
|
||||
• [bold]Presence[/bold]: Often — in person, in the body of a soldier of any race, hiding his true nature. Speaks through the flaming enthusiasm of a warrior.
|
||||
• [bold]Character[/bold]: Rude, but honest. His respect must be earned. He is not about winning — he is about fighting.
|
||||
• [bold]Attitude to mortals[/bold]: Loves those who fight not for the sake of killing, but for the sake of dignity. Tieflings are native creatures of his personal war plan.
|
||||
• [bold]Comment[/bold]: Dangerous where the cult turns war into a game. But necessary where honor is above fear.
|
||||
|
||||
{"[head=3][color=blue]Archfey[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Mother of the Embrace, Warming Wound, Flower after the Storm.[/italic]
|
||||
• [bold]Idea[/bold]: Love, kindness, family.
|
||||
• [bold]Symbol[/bold]: A loose heart with petals.
|
||||
• [bold]Presence[/bold]: Her image can appear during wedding ceremonies, and in other strong emotional moments.
|
||||
• [bold]Character[/bold]: Sincere and compassionate. Speaks like a mother.
|
||||
• [bold]Attitude to mortals[/bold]: Accepts everyone. Even the fallen. Especially them.
|
||||
• [bold]Comment[/bold]: Her faith is often ridiculed as 'weak'. But it is she who keeps people from the last line.
|
||||
|
||||
{"[head=3][color=blue]Lumera[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Guiding in the Night, Quiet Teacher, Eye of the Moon.[/italic]
|
||||
• [bold]Idea[/bold]: Night, stars, knowledge, learning.
|
||||
• [bold]Symbol[/bold]: A crescent moon with rays streaming from it.
|
||||
• [bold]Presence[/bold]: Often physically, in the form of a harpy. She is one of the few who actually walks among people. Often appears in Kraichel. Known for her public lectures, although almost no one remembers her face.
|
||||
• [bold]Character[/bold]: Wise, ironic, caring. Teaches not with words, but with questions.
|
||||
• [bold]Attitude to mortals[/bold]: Values those who seek knowledge and those who have risen from the darkness.
|
||||
• [bold]Comment[/bold]: Liars fear her. Priests try to find a connection between Lumera and the scarlet moon.
|
||||
|
||||
{"[head=3][color=blue]Silforia[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Wind of Thirst, Mistress of Adventures, Fearless.[/italic]
|
||||
• [bold]Idea[/bold]: Adventures, exploration, freedom.
|
||||
• [bold]Symbol[/bold]: Leaves in the wind.
|
||||
• [bold]Presence[/bold]: Often travels incognito. Can be anyone: a beggar, an old man, a guildmaster.
|
||||
• [bold]Character[/bold]: Energetic, wild, with an infectious laugh. Loves an argument.
|
||||
• [bold]Attitude to mortals[/bold]: Appreciates those who take risks. Despises those who live under the lock of fear.
|
||||
• [bold]Comment[/bold]: The only god you want to sit by the fire with. She laughs both for you and with you. A former supreme spirit of the wind who received a blessing from Relafir and Vestris and became a goddess after repelling the attack of the leviathan.
|
||||
|
||||
{"[head=3][color=blue]Aleona[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Court of Light, Steel Guardian, Eye of the Empire.[/italic]
|
||||
• [bold]Idea[/bold]: Justice, protection, law.
|
||||
• [bold]Symbol[/bold]: Scales, surrounded by radiance.
|
||||
• [bold]Presence[/bold]: In the halls of justice of the Zellasian Empire. Can appear in person if the trial threatens to become a farce.
|
||||
• [bold]Character[/bold]: Impeccably cold-blooded. Does not turn away from pain, but does not accept tears as an argument.
|
||||
• [bold]Attitude to mortals[/bold]: Sees them as subjects of justice. Believes in correction, but punishes without hesitation.
|
||||
• [bold]Comment[/bold]: The Empire calls her its goddess. But she serves not the throne - but the truth. And this makes her more terrible than the entire pantheon.
|
||||
|
||||
{"[head=3][color=blue]Osif[/color][/head]"}
|
||||
|
||||
• [bold]Names[/bold]: [italic]Storm, Insatiability, Sea Depths.[/italic]
|
||||
• [bold]Idea[/bold]: Steadfastness, uncertainty, sea madness.
|
||||
• [bold]Symbol[/bold]: Two waves heading towards each other.
|
||||
• [bold]Presence[/bold]: Almost never appears in the world, but his child - Leviathan - lives in the depths of the ocean and seas.
|
||||
• [bold]Character[/bold]: Explosive character, you never know what to expect from him.
|
||||
• [bold]Attitude to mortals[/bold]: Can both help and sink entire coasts and fleets, his child despises any creature.
|
||||
• [bold]Comment[/bold]: Deity of the Oceans, Waters and Storms, During the divine war he supported Tomer and with the energy of Lumera gave birth to Leviathan - a monster that during the war with Yantakini sank almost all the ships of the Zellasian empire and fought in a forgotten era with Silphoria.
|
||||
@@ -0,0 +1,32 @@
|
||||
cp14-book-text-pantheon-gods-sileita-imitators = [head=2]Imitators of the Gods[/head]
|
||||
|
||||
• [bold]Names[/bold]: [italic]Echo-Spirits, Mask-Wearers, Pseudo-Divines.[/italic]
|
||||
|
||||
Imitators are beings, spirits, phantoms or possessed that take on the appearance, speech, habits and goals of one of the gods, without their blessing or sanction. They are not true servants, but can outwardly indistinguishably copy divine manifestations. This phenomenon occurs both in ritual art and magic of persuasion, and in unconscious forms of distorted faith. There are also unique creatures created unconsciously, under the influence of magical anomalies, egregors (collective mind) of faith or the Void.
|
||||
|
||||
{"[head=2]General about the nature of Imitators[/head]"}
|
||||
|
||||
They do not receive power from the god himself, but can use rituals that imitate miracles and / or the energy of the god.
|
||||
Often appear in places of great religious tension - in empty temples, on battlefields where the gods were worshipped, or in regions forgotten by the priesthood.
|
||||
Some Mimics are spirits that arose from the echoes of a collective faith and absorbed the energy of the gods, others are mortals captivated by the idea.
|
||||
Sometimes a god knows about the Mimic, but does not react - considering it 'the noise of faith'.
|
||||
|
||||
{"[head=2]Typology of Imitators[/head]"}
|
||||
|
||||
{"[head=3][color=goldenrod]Shadow Faces[/color][/head]"}
|
||||
|
||||
Creatures that copy the appearance and habits of gods. Often they are spirits that were filled with the energy of gods at the time of evolution, or lived at that time in places of religious indignation.
|
||||
{"[italic]Example[/italic]: A spirit living at the altar of a goddess takes on her appearance and habits, becoming a weak copy."}
|
||||
|
||||
{"[head=3][color=goldenrod]Echo Spirits[/color][/head]"}
|
||||
|
||||
Incorporeal formations that arise from an excess of prayers or fear. They have no will, but reflect a specific side of the god - the wrath of Jelaryon, the compassion of the Archfey, etc. They can 'infect' people, making them temporary carriers of a behavioral pattern. They often appear next to children who have experienced religious trauma or ecstasy.
|
||||
{"[italic]Example[/italic]: the silhouette of Irumel in ruined temples calling for compassion."}
|
||||
|
||||
{"[head=3][color=goldenrod]Theomims ('godlike')[/color][/head]"}
|
||||
|
||||
A spirit that has adopted the energy of a god or void during its development and has evolved into a higher spirit, becoming closer to its god. Can show self-awareness or become possessed by other beings, become an evil caricature of a god - mnemostealers. These are evil, fanatical copies of gods. They steal the image, speech and power of a deity, distort them, act on behalf of which they did not receive. Some mnemostealers gather their own 'false cultists', convincing them of their authenticity.
|
||||
|
||||
{"[head=3][color=goldenrod]Splinters of Will[/color][/head]"}
|
||||
|
||||
Appear in places where a god has previously manifested. These are fragments of past manifestations that have lost contact with the source. They are unreasonable, but carry the habits, voice and behavior pattern of the deity. They are a 'living' memory of the events of the god at that moment. Can be dangerous if they believe that they need to 'fulfill the will' again
|
||||
1
Resources/Locale/en-US/_CP14/construction/category.ftl
Normal file
1
Resources/Locale/en-US/_CP14/construction/category.ftl
Normal file
@@ -0,0 +1 @@
|
||||
cp14-construction-category-vampire = Vampires
|
||||
@@ -1 +1,2 @@
|
||||
cp14-construction-condition-mana-filled = The structure must be fully powered by mana.
|
||||
cp14-construction-condition-singleton = Can only exist in a single copy!
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user