v1.4.4
This commit is contained in:
commit
9c94d113d3
10260 changed files with 1237388 additions and 0 deletions
522
source/game/items/StarActiveItem.cpp
Normal file
522
source/game/items/StarActiveItem.cpp
Normal file
|
@ -0,0 +1,522 @@
|
|||
#include "StarActiveItem.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarConfigLuaBindings.hpp"
|
||||
#include "StarItemLuaBindings.hpp"
|
||||
#include "StarStatusControllerLuaBindings.hpp"
|
||||
#include "StarNetworkedAnimatorLuaBindings.hpp"
|
||||
#include "StarScriptedAnimatorLuaBindings.hpp"
|
||||
#include "StarPlayerLuaBindings.hpp"
|
||||
#include "StarEntityLuaBindings.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarDataStreamExtra.hpp"
|
||||
#include "StarPlayer.hpp"
|
||||
#include "StarEmoteEntity.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ActiveItem::ActiveItem(Json const& config, String const& directory, Json const& parameters)
|
||||
: Item(config, directory, parameters) {
|
||||
auto assets = Root::singleton().assets();
|
||||
auto animationConfig = assets->fetchJson(instanceValue("animation"), directory);
|
||||
if (auto customConfig = instanceValue("animationCustom"))
|
||||
animationConfig = jsonMerge(animationConfig, customConfig);
|
||||
m_itemAnimator = NetworkedAnimator(animationConfig, directory);
|
||||
for (auto const& pair : instanceValue("animationParts", JsonObject()).iterateObject())
|
||||
m_itemAnimator.setPartTag(pair.first, "partImage", pair.second.toString());
|
||||
m_scriptedAnimationParameters.reset(config.getObject("scriptedAnimationParameters", {}));
|
||||
|
||||
addNetElement(&m_itemAnimator);
|
||||
addNetElement(&m_holdingItem);
|
||||
addNetElement(&m_backArmFrame);
|
||||
addNetElement(&m_frontArmFrame);
|
||||
addNetElement(&m_twoHandedGrip);
|
||||
addNetElement(&m_recoil);
|
||||
addNetElement(&m_outsideOfHand);
|
||||
addNetElement(&m_armAngle);
|
||||
addNetElement(&m_facingDirection);
|
||||
addNetElement(&m_damageSources);
|
||||
addNetElement(&m_itemDamageSources);
|
||||
addNetElement(&m_shieldPolys);
|
||||
addNetElement(&m_itemShieldPolys);
|
||||
addNetElement(&m_forceRegions);
|
||||
addNetElement(&m_itemForceRegions);
|
||||
|
||||
// don't interpolate scripted animation parameters
|
||||
addNetElement(&m_scriptedAnimationParameters, false);
|
||||
|
||||
m_holdingItem.set(true);
|
||||
m_armAngle.setFixedPointBase(0.01f);
|
||||
}
|
||||
|
||||
ActiveItem::ActiveItem(ActiveItem const& rhs) : ActiveItem(rhs.config(), rhs.directory(), rhs.parameters()) {}
|
||||
|
||||
ItemPtr ActiveItem::clone() const {
|
||||
return make_shared<ActiveItem>(*this);
|
||||
}
|
||||
|
||||
void ActiveItem::init(ToolUserEntity* owner, ToolHand hand) {
|
||||
ToolUserItem::init(owner, hand);
|
||||
if (entityMode() == EntityMode::Master) {
|
||||
m_script.setScripts(jsonToStringList(instanceValue("scripts")).transformed(bind(&AssetPath::relativeTo, directory(), _1)));
|
||||
m_script.setUpdateDelta(instanceValue("scriptDelta", 1).toUInt());
|
||||
m_twoHandedGrip.set(twoHanded());
|
||||
|
||||
if (auto previousStorage = instanceValue("scriptStorage"))
|
||||
m_script.setScriptStorage(previousStorage.toObject());
|
||||
|
||||
m_script.addCallbacks("activeItem", makeActiveItemCallbacks());
|
||||
m_script.addCallbacks("item", LuaBindings::makeItemCallbacks(this));
|
||||
m_script.addCallbacks("config", LuaBindings::makeConfigCallbacks(bind(&Item::instanceValue, as<Item>(this), _1, _2)));
|
||||
m_script.addCallbacks("animator", LuaBindings::makeNetworkedAnimatorCallbacks(&m_itemAnimator));
|
||||
m_script.addCallbacks("status", LuaBindings::makeStatusControllerCallbacks(owner->statusController()));
|
||||
m_script.addActorMovementCallbacks(owner->movementController());
|
||||
if (auto player = as<Player>(owner))
|
||||
m_script.addCallbacks("player", LuaBindings::makePlayerCallbacks(player));
|
||||
m_script.addCallbacks("entity", LuaBindings::makeEntityCallbacks(as<Entity>(owner)));
|
||||
m_script.init(world());
|
||||
m_currentFireMode = FireMode::None;
|
||||
}
|
||||
if (world()->isClient()) {
|
||||
if (auto animationScripts = instanceValue("animationScripts")) {
|
||||
m_scriptedAnimator.setScripts(jsonToStringList(animationScripts).transformed(bind(&AssetPath::relativeTo, directory(), _1)));
|
||||
m_scriptedAnimator.setUpdateDelta(instanceValue("animationDelta", 1).toUInt());
|
||||
|
||||
m_scriptedAnimator.addCallbacks("animationConfig", LuaBindings::makeScriptedAnimatorCallbacks(&m_itemAnimator,
|
||||
[this](String const& name, Json const& defaultValue) -> Json {
|
||||
return m_scriptedAnimationParameters.value(name, defaultValue);
|
||||
}));
|
||||
m_scriptedAnimator.addCallbacks("activeItemAnimation", makeScriptedAnimationCallbacks());
|
||||
m_scriptedAnimator.addCallbacks("config", LuaBindings::makeConfigCallbacks(bind(&Item::instanceValue, as<Item>(this), _1, _2)));
|
||||
m_scriptedAnimator.init(world());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveItem::uninit() {
|
||||
if (entityMode() == EntityMode::Master) {
|
||||
m_script.uninit();
|
||||
m_script.removeCallbacks("activeItem");
|
||||
m_script.removeCallbacks("item");
|
||||
m_script.removeCallbacks("config");
|
||||
m_script.removeCallbacks("animator");
|
||||
m_script.removeCallbacks("status");
|
||||
m_script.removeActorMovementCallbacks();
|
||||
m_script.removeCallbacks("player");
|
||||
m_script.removeCallbacks("entity");
|
||||
}
|
||||
if (world()->isClient()) {
|
||||
if (auto animationScripts = instanceValue("animationScripts")) {
|
||||
m_scriptedAnimator.uninit();
|
||||
m_scriptedAnimator.removeCallbacks("animationConfig");
|
||||
m_scriptedAnimator.removeCallbacks("activeItemAnimation");
|
||||
m_scriptedAnimator.removeCallbacks("config");
|
||||
}
|
||||
}
|
||||
|
||||
m_itemAnimatorDynamicTarget.stopAudio();
|
||||
ToolUserItem::uninit();
|
||||
m_activeAudio.clear();
|
||||
}
|
||||
|
||||
void ActiveItem::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
|
||||
StringMap<bool> moveMap;
|
||||
for (auto m : moves)
|
||||
moveMap[MoveControlTypeNames.getRight(m)] = true;
|
||||
|
||||
if (entityMode() == EntityMode::Master) {
|
||||
if (fireMode != m_currentFireMode) {
|
||||
m_currentFireMode = fireMode;
|
||||
if (fireMode != FireMode::None)
|
||||
m_script.invoke("activate", FireModeNames.getRight(fireMode), shifting, moveMap);
|
||||
}
|
||||
m_script.update(m_script.updateDt(), FireModeNames.getRight(fireMode), shifting, moveMap);
|
||||
|
||||
if (instanceValue("retainScriptStorageInItem", false).toBool()) {
|
||||
setInstanceValue("scriptStorage", m_script.getScriptStorage());
|
||||
}
|
||||
}
|
||||
|
||||
if (world()->isClient()) {
|
||||
m_itemAnimator.update(WorldTimestep, &m_itemAnimatorDynamicTarget);
|
||||
m_scriptedAnimator.update(m_scriptedAnimator.updateDt());
|
||||
} else {
|
||||
m_itemAnimator.update(WorldTimestep, nullptr);
|
||||
}
|
||||
|
||||
eraseWhere(m_activeAudio, [this](pair<AudioInstancePtr const, Vec2F> const& a) {
|
||||
a.first->setPosition(owner()->position() + handPosition(a.second));
|
||||
return a.first->finished();
|
||||
});
|
||||
|
||||
for (auto shieldPoly : shieldPolys()) {
|
||||
shieldPoly.translate(owner()->position());
|
||||
SpatialLogger::logPoly("world", shieldPoly, {255, 255, 0, 255});
|
||||
}
|
||||
|
||||
for (auto forceRegion : forceRegions()) {
|
||||
if (auto dfr = forceRegion.ptr<DirectionalForceRegion>())
|
||||
SpatialLogger::logPoly("world", dfr->region, {155, 0, 255, 255});
|
||||
else if (auto rfr = forceRegion.ptr<RadialForceRegion>())
|
||||
SpatialLogger::logPoint("world", rfr->center, {155, 0, 255, 255});
|
||||
}
|
||||
}
|
||||
|
||||
List<DamageSource> ActiveItem::damageSources() const {
|
||||
List<DamageSource> damageSources = m_damageSources.get();
|
||||
for (auto ds : m_itemDamageSources.get()) {
|
||||
if (ds.damageArea.is<PolyF>()) {
|
||||
auto& poly = ds.damageArea.get<PolyF>();
|
||||
poly.rotate(m_armAngle.get());
|
||||
if (owner()->facingDirection() == Direction::Left)
|
||||
poly.flipHorizontal(0.0f);
|
||||
poly.translate(handPosition(Vec2F()));
|
||||
} else if (ds.damageArea.is<Line2F>()) {
|
||||
auto& line = ds.damageArea.get<Line2F>();
|
||||
line.rotate(m_armAngle.get());
|
||||
if (owner()->facingDirection() == Direction::Left)
|
||||
line.flipHorizontal(0.0f);
|
||||
line.translate(handPosition(Vec2F()));
|
||||
}
|
||||
damageSources.append(move(ds));
|
||||
}
|
||||
return damageSources;
|
||||
}
|
||||
|
||||
List<PolyF> ActiveItem::shieldPolys() const {
|
||||
List<PolyF> shieldPolys = m_shieldPolys.get();
|
||||
for (auto sp : m_itemShieldPolys.get()) {
|
||||
sp.rotate(m_armAngle.get());
|
||||
if (owner()->facingDirection() == Direction::Left)
|
||||
sp.flipHorizontal(0.0f);
|
||||
sp.translate(handPosition(Vec2F()));
|
||||
shieldPolys.append(move(sp));
|
||||
}
|
||||
return shieldPolys;
|
||||
}
|
||||
|
||||
List<PhysicsForceRegion> ActiveItem::forceRegions() const {
|
||||
List<PhysicsForceRegion> forceRegions = m_forceRegions.get();
|
||||
for (auto fr : m_itemForceRegions.get()) {
|
||||
if (auto dfr = fr.ptr<DirectionalForceRegion>()) {
|
||||
dfr->region.rotate(m_armAngle.get());
|
||||
if (owner()->facingDirection() == Direction::Left)
|
||||
dfr->region.flipHorizontal(0.0f);
|
||||
dfr->region.translate(owner()->position() + handPosition(Vec2F()));
|
||||
} else if (auto rfr = fr.ptr<RadialForceRegion>()) {
|
||||
rfr->center = rfr->center.rotate(m_armAngle.get());
|
||||
if (owner()->facingDirection() == Direction::Left)
|
||||
rfr->center[0] *= -1;
|
||||
rfr->center += owner()->position() + handPosition(Vec2F());
|
||||
}
|
||||
forceRegions.append(move(fr));
|
||||
}
|
||||
return forceRegions;
|
||||
}
|
||||
|
||||
bool ActiveItem::holdingItem() const {
|
||||
return m_holdingItem.get();
|
||||
}
|
||||
|
||||
Maybe<String> ActiveItem::backArmFrame() const {
|
||||
return m_backArmFrame.get();
|
||||
}
|
||||
|
||||
Maybe<String> ActiveItem::frontArmFrame() const {
|
||||
return m_frontArmFrame.get();
|
||||
}
|
||||
|
||||
bool ActiveItem::twoHandedGrip() const {
|
||||
return m_twoHandedGrip.get();
|
||||
}
|
||||
|
||||
bool ActiveItem::recoil() const {
|
||||
return m_recoil.get();
|
||||
}
|
||||
|
||||
bool ActiveItem::outsideOfHand() const {
|
||||
return m_outsideOfHand.get();
|
||||
}
|
||||
|
||||
float ActiveItem::armAngle() const {
|
||||
return m_armAngle.get();
|
||||
}
|
||||
|
||||
Maybe<Direction> ActiveItem::facingDirection() const {
|
||||
return m_facingDirection.get();
|
||||
}
|
||||
|
||||
List<Drawable> ActiveItem::handDrawables() const {
|
||||
if (m_itemAnimator.parts().empty()) {
|
||||
auto drawables = Item::iconDrawables();
|
||||
Drawable::scaleAll(drawables, 1.0f / TilePixels);
|
||||
return drawables;
|
||||
} else {
|
||||
return m_itemAnimator.drawables();
|
||||
}
|
||||
}
|
||||
|
||||
List<pair<Drawable, Maybe<EntityRenderLayer>>> ActiveItem::entityDrawables() const {
|
||||
return m_scriptedAnimator.drawables();
|
||||
}
|
||||
|
||||
List<LightSource> ActiveItem::lights() const {
|
||||
// Same as pullNewAudios, we translate and flip ourselves.
|
||||
List<LightSource> result;
|
||||
for (auto& light : m_itemAnimator.lightSources()) {
|
||||
light.position = owner()->position() + handPosition(light.position);
|
||||
light.beamAngle += m_armAngle.get();
|
||||
if (owner()->facingDirection() == Direction::Left) {
|
||||
if (light.beamAngle > 0)
|
||||
light.beamAngle = Constants::pi / 2 + constrainAngle(Constants::pi / 2 - light.beamAngle);
|
||||
else
|
||||
light.beamAngle = -Constants::pi / 2 - constrainAngle(light.beamAngle + Constants::pi / 2);
|
||||
}
|
||||
result.append(move(light));
|
||||
}
|
||||
result.appendAll(m_scriptedAnimator.lightSources());
|
||||
return result;
|
||||
}
|
||||
|
||||
List<AudioInstancePtr> ActiveItem::pullNewAudios() {
|
||||
// Because the item animator is in hand-space, and Humanoid does all the
|
||||
// translation *and flipping*, we cannot use NetworkedAnimator's built-in
|
||||
// functionality to rotate and flip, and instead must do it manually. We do
|
||||
// not call animatorTarget.setPosition, and keep track of running audio
|
||||
// ourselves. It would be easier if (0, 0) for the NetworkedAnimator was,
|
||||
// say, the shoulder and un-rotated, but it gets a bit weird with Humanoid
|
||||
// modifications.
|
||||
List<AudioInstancePtr> result;
|
||||
for (auto& audio : m_itemAnimatorDynamicTarget.pullNewAudios()) {
|
||||
m_activeAudio[audio] = *audio->position();
|
||||
audio->setPosition(owner()->position() + handPosition(*audio->position()));
|
||||
result.append(move(audio));
|
||||
}
|
||||
result.appendAll(m_scriptedAnimator.pullNewAudios());
|
||||
return result;
|
||||
}
|
||||
|
||||
List<Particle> ActiveItem::pullNewParticles() {
|
||||
// Same as pullNewAudios, we translate, rotate, and flip ourselves
|
||||
List<Particle> result;
|
||||
for (auto& particle : m_itemAnimatorDynamicTarget.pullNewParticles()) {
|
||||
particle.position = owner()->position() + handPosition(particle.position);
|
||||
particle.velocity = particle.velocity.rotate(m_armAngle.get());
|
||||
if (owner()->facingDirection() == Direction::Left) {
|
||||
particle.velocity[0] *= -1;
|
||||
particle.flip = !particle.flip;
|
||||
}
|
||||
result.append(move(particle));
|
||||
}
|
||||
result.appendAll(m_scriptedAnimator.pullNewParticles());
|
||||
return result;
|
||||
}
|
||||
|
||||
Maybe<String> ActiveItem::cursor() const {
|
||||
return m_cursor;
|
||||
}
|
||||
|
||||
Maybe<Json> ActiveItem::receiveMessage(String const& message, bool localMessage, JsonArray const& args) {
|
||||
return m_script.handleMessage(message, localMessage, args);
|
||||
}
|
||||
|
||||
float ActiveItem::durabilityStatus() {
|
||||
auto durability = instanceValue("durability").optFloat();
|
||||
if (durability) {
|
||||
auto durabilityHit = instanceValue("durabilityHit").optFloat().value(*durability);
|
||||
return durabilityHit / *durability;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
Vec2F ActiveItem::armPosition(Vec2F const& offset) const {
|
||||
return owner()->armPosition(hand(), owner()->facingDirection(), m_armAngle.get(), offset);
|
||||
}
|
||||
|
||||
Vec2F ActiveItem::handPosition(Vec2F const& offset) const {
|
||||
return armPosition(offset + owner()->handOffset(hand(), owner()->facingDirection()));
|
||||
}
|
||||
|
||||
LuaCallbacks ActiveItem::makeActiveItemCallbacks() {
|
||||
LuaCallbacks callbacks;
|
||||
callbacks.registerCallback("ownerEntityId", [this]() {
|
||||
return owner()->entityId();
|
||||
});
|
||||
callbacks.registerCallback("ownerTeam", [this]() {
|
||||
return owner()->getTeam().toJson();
|
||||
});
|
||||
callbacks.registerCallback("ownerAimPosition", [this]() {
|
||||
return owner()->aimPosition();
|
||||
});
|
||||
callbacks.registerCallback("ownerPowerMultiplier", [this]() {
|
||||
return owner()->powerMultiplier();
|
||||
});
|
||||
callbacks.registerCallback("fireMode", [this]() {
|
||||
return FireModeNames.getRight(m_currentFireMode);
|
||||
});
|
||||
callbacks.registerCallback("hand", [this]() {
|
||||
return ToolHandNames.getRight(hand());
|
||||
});
|
||||
callbacks.registerCallback("handPosition", [this](Maybe<Vec2F> offset) {
|
||||
return handPosition(offset.value());
|
||||
});
|
||||
|
||||
// Gets the required aim angle to aim a "barrel" of the item that has the given
|
||||
// vertical offset from the hand at the given target. The line that is aimed
|
||||
// at the target is the horizontal line going through the aimVerticalOffset.
|
||||
callbacks.registerCallback("aimAngleAndDirection", [this](float aimVerticalOffset, Vec2F targetPosition) {
|
||||
// This was figured out using pencil and paper geometry from the hand
|
||||
// rotation center, the target position, and the 90 deg vertical offset of
|
||||
// the "barrel".
|
||||
|
||||
Vec2F handRotationCenter = owner()->armPosition(hand(), owner()->facingDirection(), 0.0f, Vec2F());
|
||||
Vec2F ownerPosition = owner()->position();
|
||||
|
||||
// Vector in owner entity space from hand rotation center to target.
|
||||
Vec2F toTarget = owner()->world()->geometry().diff(targetPosition, (ownerPosition + handRotationCenter));
|
||||
float toTargetDist = toTarget.magnitude();
|
||||
|
||||
// If the aim position is inside the circle formed by the barrel line as it
|
||||
// goes around (aimVerticalOffset <= toTargetDist) absolutely no angle will
|
||||
// give you an intersect, so we just bail out and assume the target is at the
|
||||
// edge of the circle to retain continuity.
|
||||
float angleAdjust = -std::asin(clamp(aimVerticalOffset / toTargetDist, -1.0f, 1.0f));
|
||||
auto angleSide = getAngleSide(toTarget.angle());
|
||||
return luaTupleReturn(angleSide.first + angleAdjust, numericalDirection(angleSide.second));
|
||||
});
|
||||
|
||||
// Similar to aimAngleAndDirection, but only provides the offset-adjusted aimAngle for the current facing direction
|
||||
callbacks.registerCallback("aimAngle", [this](float aimVerticalOffset, Vec2F targetPosition) {
|
||||
Vec2F handRotationCenter = owner()->armPosition(hand(), owner()->facingDirection(), 0.0f, Vec2F());
|
||||
Vec2F ownerPosition = owner()->position();
|
||||
Vec2F toTarget = owner()->world()->geometry().diff(targetPosition, (ownerPosition + handRotationCenter));
|
||||
float toTargetDist = toTarget.magnitude();
|
||||
float angleAdjust = -std::asin(clamp(aimVerticalOffset / toTargetDist, -1.0f, 1.0f));
|
||||
return toTarget.angle() + angleAdjust;
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setHoldingItem", [this](bool holdingItem) {
|
||||
m_holdingItem.set(holdingItem);
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setBackArmFrame", [this](Maybe<String> armFrame) {
|
||||
m_backArmFrame.set(armFrame);
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setFrontArmFrame", [this](Maybe<String> armFrame) {
|
||||
m_frontArmFrame.set(armFrame);
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setTwoHandedGrip", [this](bool twoHandedGrip) {
|
||||
m_twoHandedGrip.set(twoHandedGrip);
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setRecoil", [this](bool recoil) {
|
||||
m_recoil.set(recoil);
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setOutsideOfHand", [this](bool outsideOfHand) {
|
||||
m_outsideOfHand.set(outsideOfHand);
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setArmAngle", [this](float armAngle) {
|
||||
m_armAngle.set(armAngle);
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setFacingDirection", [this](float direction) {
|
||||
m_facingDirection.set(directionOf(direction));
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setDamageSources", [this](Maybe<JsonArray> const& damageSources) {
|
||||
m_damageSources.set(damageSources.value().transformed(construct<DamageSource>()));
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setItemDamageSources", [this](Maybe<JsonArray> const& damageSources) {
|
||||
m_itemDamageSources.set(damageSources.value().transformed(construct<DamageSource>()));
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setShieldPolys", [this](Maybe<List<PolyF>> const& shieldPolys) {
|
||||
m_shieldPolys.set(shieldPolys.value());
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setItemShieldPolys", [this](Maybe<List<PolyF>> const& shieldPolys) {
|
||||
m_itemShieldPolys.set(shieldPolys.value());
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setForceRegions", [this](Maybe<JsonArray> const& forceRegions) {
|
||||
if (forceRegions)
|
||||
m_forceRegions.set(forceRegions->transformed(jsonToPhysicsForceRegion));
|
||||
else
|
||||
m_forceRegions.set({});
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setItemForceRegions", [this](Maybe<JsonArray> const& forceRegions) {
|
||||
if (forceRegions)
|
||||
m_itemForceRegions.set(forceRegions->transformed(jsonToPhysicsForceRegion));
|
||||
else
|
||||
m_itemForceRegions.set({});
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setCursor", [this](Maybe<String> cursor) {
|
||||
m_cursor = move(cursor);
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setScriptedAnimationParameter", [this](String name, Json value) {
|
||||
m_scriptedAnimationParameters.set(move(name), move(value));
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setInventoryIcon", [this](String image) {
|
||||
setInstanceValue("inventoryIcon", image);
|
||||
setIconDrawables({Drawable::makeImage(move(image), 1.0f, true, Vec2F())});
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setInstanceValue", [this](String name, Json val) {
|
||||
setInstanceValue(move(name), move(val));
|
||||
});
|
||||
|
||||
callbacks.registerCallback("callOtherHandScript", [this](String const& func, LuaVariadic<LuaValue> const& args) {
|
||||
if (auto otherHandItem = owner()->handItem(hand() == ToolHand::Primary ? ToolHand::Alt : ToolHand::Primary)) {
|
||||
if (auto otherActiveItem = as<ActiveItem>(otherHandItem))
|
||||
return otherActiveItem->m_script.invoke(func, args).value();
|
||||
}
|
||||
return LuaValue();
|
||||
});
|
||||
|
||||
callbacks.registerCallback("interact", [this](String const& type, Json const& configData, Maybe<EntityId> const& sourceEntityId) {
|
||||
owner()->interact(InteractAction(type, sourceEntityId.value(NullEntityId), configData));
|
||||
});
|
||||
|
||||
callbacks.registerCallback("emote", [this](String const& emoteName) {
|
||||
auto emote = HumanoidEmoteNames.getLeft(emoteName);
|
||||
if (auto entity = as<EmoteEntity>(owner()))
|
||||
entity->playEmote(emote);
|
||||
});
|
||||
|
||||
callbacks.registerCallback("setCameraFocusEntity", [this](Maybe<EntityId> const& cameraFocusEntity) {
|
||||
owner()->setCameraFocusEntity(cameraFocusEntity);
|
||||
});
|
||||
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
LuaCallbacks ActiveItem::makeScriptedAnimationCallbacks() {
|
||||
LuaCallbacks callbacks;
|
||||
callbacks.registerCallback("ownerPosition", [this]() {
|
||||
return owner()->position();
|
||||
});
|
||||
callbacks.registerCallback("ownerAimPosition", [this]() {
|
||||
return owner()->aimPosition();
|
||||
});
|
||||
callbacks.registerCallback("ownerArmAngle", [this]() {
|
||||
return m_armAngle.get();
|
||||
});
|
||||
callbacks.registerCallback("ownerFacingDirection", [this]() {
|
||||
return numericalDirection(owner()->facingDirection());
|
||||
});
|
||||
callbacks.registerCallback("handPosition", [this](Maybe<Vec2F> offset) {
|
||||
return handPosition(offset.value());
|
||||
});
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
}
|
101
source/game/items/StarActiveItem.hpp
Normal file
101
source/game/items/StarActiveItem.hpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
#ifndef STAR_ACTIVE_ITEM_HPP
|
||||
#define STAR_ACTIVE_ITEM_HPP
|
||||
|
||||
#include "StarNetElementBasicFields.hpp"
|
||||
#include "StarNetElementFloatFields.hpp"
|
||||
#include "StarItem.hpp"
|
||||
#include "StarToolUserItem.hpp"
|
||||
#include "StarLuaComponents.hpp"
|
||||
#include "StarLuaActorMovementComponent.hpp"
|
||||
#include "StarNetworkedAnimator.hpp"
|
||||
#include "StarLuaAnimationComponent.hpp"
|
||||
#include "StarDurabilityItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(AudioInstance);
|
||||
STAR_CLASS(ActiveItem);
|
||||
|
||||
class ActiveItem :
|
||||
public Item,
|
||||
public DurabilityItem,
|
||||
public virtual ToolUserItem,
|
||||
public virtual NetElementGroup {
|
||||
public:
|
||||
ActiveItem(Json const& config, String const& directory, Json const& parameters = JsonObject());
|
||||
ActiveItem(ActiveItem const& rhs);
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
void init(ToolUserEntity* owner, ToolHand hand) override;
|
||||
void uninit() override;
|
||||
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
|
||||
List<DamageSource> damageSources() const override;
|
||||
List<PolyF> shieldPolys() const override;
|
||||
|
||||
List<PhysicsForceRegion> forceRegions() const override;
|
||||
|
||||
bool holdingItem() const;
|
||||
Maybe<String> backArmFrame() const;
|
||||
Maybe<String> frontArmFrame() const;
|
||||
bool twoHandedGrip() const;
|
||||
bool recoil() const;
|
||||
bool outsideOfHand() const;
|
||||
|
||||
float armAngle() const;
|
||||
Maybe<Direction> facingDirection() const;
|
||||
|
||||
// Hand drawables are in hand-space, everything else is in world space.
|
||||
List<Drawable> handDrawables() const;
|
||||
List<pair<Drawable, Maybe<EntityRenderLayer>>> entityDrawables() const;
|
||||
List<LightSource> lights() const;
|
||||
List<AudioInstancePtr> pullNewAudios();
|
||||
List<Particle> pullNewParticles();
|
||||
|
||||
Maybe<String> cursor() const;
|
||||
|
||||
Maybe<Json> receiveMessage(String const& message, bool localMessage, JsonArray const& args = {});
|
||||
|
||||
float durabilityStatus() override;
|
||||
|
||||
private:
|
||||
Vec2F armPosition(Vec2F const& offset) const;
|
||||
Vec2F handPosition(Vec2F const& offset) const;
|
||||
|
||||
LuaCallbacks makeActiveItemCallbacks();
|
||||
LuaCallbacks makeScriptedAnimationCallbacks();
|
||||
|
||||
mutable LuaMessageHandlingComponent<LuaActorMovementComponent<LuaUpdatableComponent<LuaStorableComponent<LuaWorldComponent<LuaBaseComponent>>>>> m_script;
|
||||
|
||||
NetworkedAnimator m_itemAnimator;
|
||||
NetworkedAnimator::DynamicTarget m_itemAnimatorDynamicTarget;
|
||||
|
||||
mutable LuaAnimationComponent<LuaUpdatableComponent<LuaWorldComponent<LuaBaseComponent>>> m_scriptedAnimator;
|
||||
|
||||
HashMap<AudioInstancePtr, Vec2F> m_activeAudio;
|
||||
|
||||
FireMode m_currentFireMode;
|
||||
Maybe<String> m_cursor;
|
||||
|
||||
NetElementBool m_holdingItem;
|
||||
NetElementData<Maybe<String>> m_backArmFrame;
|
||||
NetElementData<Maybe<String>> m_frontArmFrame;
|
||||
NetElementBool m_twoHandedGrip;
|
||||
NetElementBool m_recoil;
|
||||
NetElementBool m_outsideOfHand;
|
||||
NetElementFloat m_armAngle;
|
||||
NetElementData<Maybe<Direction>> m_facingDirection;
|
||||
NetElementData<List<DamageSource>> m_damageSources;
|
||||
NetElementData<List<DamageSource>> m_itemDamageSources;
|
||||
NetElementData<List<PolyF>> m_shieldPolys;
|
||||
NetElementData<List<PolyF>> m_itemShieldPolys;
|
||||
NetElementData<List<PhysicsForceRegion>> m_forceRegions;
|
||||
NetElementData<List<PhysicsForceRegion>> m_itemForceRegions;
|
||||
NetElementHashMap<String, Json> m_scriptedAnimationParameters;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
203
source/game/items/StarArmors.cpp
Normal file
203
source/game/items/StarArmors.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
#include "StarArmors.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarImageProcessing.hpp"
|
||||
#include "StarHumanoid.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarStoredFunctions.hpp"
|
||||
#include "StarPlayer.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ArmorItem::ArmorItem(Json const& config, String const& directory, Json const& data) : Item(config, directory, data) {
|
||||
refreshStatusEffects();
|
||||
m_effectSources = jsonToStringSet(instanceValue("effectSources", JsonArray()));
|
||||
m_techModule = instanceValue("techModule", "").toString();
|
||||
if (m_techModule->empty())
|
||||
m_techModule = {};
|
||||
else
|
||||
m_techModule = AssetPath::relativeTo(directory, *m_techModule);
|
||||
|
||||
m_directives = instanceValue("directives", "").toString();
|
||||
m_colorOptions = colorDirectivesFromConfig(config.getArray("colorOptions", JsonArray{""}));
|
||||
if (m_directives.empty())
|
||||
m_directives = "?" + m_colorOptions.wrap(instanceValue("colorIndex", 0).toUInt());
|
||||
refreshIconDrawables();
|
||||
|
||||
m_hideBody = config.getBool("hideBody", false);
|
||||
}
|
||||
|
||||
List<PersistentStatusEffect> ArmorItem::statusEffects() const {
|
||||
return m_statusEffects;
|
||||
}
|
||||
|
||||
StringSet ArmorItem::effectSources() const {
|
||||
return m_effectSources;
|
||||
}
|
||||
|
||||
List<String> const& ArmorItem::colorOptions() {
|
||||
return m_colorOptions;
|
||||
}
|
||||
|
||||
String const& ArmorItem::directives() const {
|
||||
return m_directives;
|
||||
}
|
||||
|
||||
bool ArmorItem::hideBody() const {
|
||||
return m_hideBody;
|
||||
}
|
||||
|
||||
Maybe<String> const& ArmorItem::techModule() const {
|
||||
return m_techModule;
|
||||
}
|
||||
|
||||
void ArmorItem::refreshIconDrawables() {
|
||||
auto drawables = iconDrawables();
|
||||
for (auto& drawable : drawables) {
|
||||
if (drawable.isImage()) {
|
||||
drawable.imagePart().removeDirectives(true);
|
||||
drawable.imagePart().addDirectives(m_directives, true);
|
||||
}
|
||||
}
|
||||
setIconDrawables(move(drawables));
|
||||
}
|
||||
|
||||
void ArmorItem::refreshStatusEffects() {
|
||||
m_statusEffects = instanceValue("statusEffects", JsonArray()).toArray().transformed(jsonToPersistentStatusEffect);
|
||||
if (auto leveledStatusEffects = instanceValue("leveledStatusEffects", Json())) {
|
||||
auto functionDatabase = Root::singleton().functionDatabase();
|
||||
float level = instanceValue("level", 1).toFloat();
|
||||
for (auto effectConfig : leveledStatusEffects.iterateArray()) {
|
||||
float levelFunctionFactor = functionDatabase->function(effectConfig.getString("levelFunction"))->evaluate(level);
|
||||
auto statModifier = jsonToStatModifier(effectConfig);
|
||||
if (auto p = statModifier.ptr<StatBaseMultiplier>())
|
||||
p->baseMultiplier = 1 + (p->baseMultiplier - 1) * levelFunctionFactor;
|
||||
else if (auto p = statModifier.ptr<StatValueModifier>())
|
||||
p->value *= levelFunctionFactor;
|
||||
else if (auto p = statModifier.ptr<StatEffectiveMultiplier>())
|
||||
p->effectiveMultiplier = 1 + (p->effectiveMultiplier - 1) * levelFunctionFactor;
|
||||
m_statusEffects.append(statModifier);
|
||||
}
|
||||
}
|
||||
if (auto augmentConfig = instanceValue("currentAugment", Json()))
|
||||
m_statusEffects.appendAll(augmentConfig.getArray("effects", JsonArray()).transformed(jsonToPersistentStatusEffect));
|
||||
}
|
||||
|
||||
HeadArmor::HeadArmor(Json const& config, String const& directory, Json const& data)
|
||||
: ArmorItem(config, directory, data) {
|
||||
m_maleImage = AssetPath::relativeTo(directory, config.getString("maleFrames"));
|
||||
m_femaleImage = AssetPath::relativeTo(directory, config.getString("femaleFrames"));
|
||||
|
||||
m_maskDirectives = instanceValue("mask").toString();
|
||||
if (!m_maskDirectives.empty() && !m_maskDirectives.contains("?"))
|
||||
m_maskDirectives = "?addmask=" + AssetPath::relativeTo(directory, m_maskDirectives) + ";0;0";
|
||||
}
|
||||
|
||||
ItemPtr HeadArmor::clone() const {
|
||||
return make_shared<HeadArmor>(*this);
|
||||
}
|
||||
|
||||
String const& HeadArmor::frameset(Gender gender) const {
|
||||
if (gender == Gender::Male)
|
||||
return m_maleImage;
|
||||
else
|
||||
return m_femaleImage;
|
||||
}
|
||||
|
||||
String const& HeadArmor::maskDirectives() const {
|
||||
return m_maskDirectives;
|
||||
}
|
||||
|
||||
List<Drawable> HeadArmor::preview(PlayerPtr const& viewer) const {
|
||||
Gender gender = viewer ? viewer->gender() : Gender::Male;
|
||||
return Humanoid::renderDummy(gender, this);
|
||||
}
|
||||
|
||||
ChestArmor::ChestArmor(Json const& config, String const& directory, Json const& data)
|
||||
: ArmorItem(config, directory, data) {
|
||||
Json maleImages = config.get("maleFrames");
|
||||
m_maleBodyImage = AssetPath::relativeTo(directory, maleImages.getString("body"));
|
||||
m_maleFrontSleeveImage = AssetPath::relativeTo(directory, maleImages.getString("frontSleeve"));
|
||||
m_maleBackSleeveImage = AssetPath::relativeTo(directory, maleImages.getString("backSleeve"));
|
||||
|
||||
Json femaleImages = config.get("femaleFrames");
|
||||
m_femaleBodyImage = AssetPath::relativeTo(directory, femaleImages.getString("body"));
|
||||
m_femaleFrontSleeveImage = AssetPath::relativeTo(directory, femaleImages.getString("frontSleeve"));
|
||||
m_femaleBackSleeveImage = AssetPath::relativeTo(directory, femaleImages.getString("backSleeve"));
|
||||
}
|
||||
|
||||
ItemPtr ChestArmor::clone() const {
|
||||
return make_shared<ChestArmor>(*this);
|
||||
}
|
||||
|
||||
String const& ChestArmor::bodyFrameset(Gender gender) const {
|
||||
if (gender == Gender::Male)
|
||||
return m_maleBodyImage;
|
||||
else
|
||||
return m_femaleBodyImage;
|
||||
}
|
||||
|
||||
String const& ChestArmor::frontSleeveFrameset(Gender gender) const {
|
||||
if (gender == Gender::Male)
|
||||
return m_maleFrontSleeveImage;
|
||||
else
|
||||
return m_femaleFrontSleeveImage;
|
||||
}
|
||||
|
||||
String const& ChestArmor::backSleeveFrameset(Gender gender) const {
|
||||
if (gender == Gender::Male)
|
||||
return m_maleBackSleeveImage;
|
||||
else
|
||||
return m_femaleBackSleeveImage;
|
||||
}
|
||||
|
||||
List<Drawable> ChestArmor::preview(PlayerPtr const& viewer) const {
|
||||
Gender gender = viewer ? viewer->gender() : Gender::Male;
|
||||
return Humanoid::renderDummy(gender, nullptr, this);
|
||||
}
|
||||
|
||||
LegsArmor::LegsArmor(Json const& config, String const& directory, Json const& data)
|
||||
: ArmorItem(config, directory, data) {
|
||||
m_maleImage = AssetPath::relativeTo(directory, config.getString("maleFrames"));
|
||||
m_femaleImage = AssetPath::relativeTo(directory, config.getString("femaleFrames"));
|
||||
}
|
||||
|
||||
ItemPtr LegsArmor::clone() const {
|
||||
return make_shared<LegsArmor>(*this);
|
||||
}
|
||||
|
||||
String const& LegsArmor::frameset(Gender gender) const {
|
||||
if (gender == Gender::Male)
|
||||
return m_maleImage;
|
||||
else
|
||||
return m_femaleImage;
|
||||
}
|
||||
|
||||
List<Drawable> LegsArmor::preview(PlayerPtr const& viewer) const {
|
||||
Gender gender = viewer ? viewer->gender() : Gender::Male;
|
||||
return Humanoid::renderDummy(gender, nullptr, nullptr, this);
|
||||
}
|
||||
|
||||
BackArmor::BackArmor(Json const& config, String const& directory, Json const& data)
|
||||
: ArmorItem(config, directory, data) {
|
||||
m_maleImage = AssetPath::relativeTo(directory, config.getString("maleFrames"));
|
||||
m_femaleImage = AssetPath::relativeTo(directory, config.getString("femaleFrames"));
|
||||
}
|
||||
|
||||
ItemPtr BackArmor::clone() const {
|
||||
return make_shared<BackArmor>(*this);
|
||||
}
|
||||
|
||||
String const& BackArmor::frameset(Gender gender) const {
|
||||
if (gender == Gender::Male)
|
||||
return m_maleImage;
|
||||
else
|
||||
return m_femaleImage;
|
||||
}
|
||||
|
||||
List<Drawable> BackArmor::preview(PlayerPtr const& viewer) const {
|
||||
Gender gender = viewer ? viewer->gender() : Gender::Male;
|
||||
return Humanoid::renderDummy(gender, nullptr, nullptr, nullptr, this);
|
||||
}
|
||||
|
||||
}
|
127
source/game/items/StarArmors.hpp
Normal file
127
source/game/items/StarArmors.hpp
Normal file
|
@ -0,0 +1,127 @@
|
|||
#ifndef STAR_ARMORS_HPP
|
||||
#define STAR_ARMORS_HPP
|
||||
|
||||
#include "StarGameTypes.hpp"
|
||||
#include "StarItem.hpp"
|
||||
#include "StarStatusEffectItem.hpp"
|
||||
#include "StarEffectSourceItem.hpp"
|
||||
#include "StarPreviewableItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ArmorItem);
|
||||
STAR_CLASS(HeadArmor);
|
||||
STAR_CLASS(ChestArmor);
|
||||
STAR_CLASS(LegsArmor);
|
||||
STAR_CLASS(BackArmor);
|
||||
|
||||
class ArmorItem : public Item, public StatusEffectItem, public EffectSourceItem {
|
||||
public:
|
||||
ArmorItem(Json const& config, String const& directory, Json const& data);
|
||||
virtual ~ArmorItem() {}
|
||||
|
||||
virtual List<PersistentStatusEffect> statusEffects() const override;
|
||||
virtual StringSet effectSources() const override;
|
||||
|
||||
List<String> const& colorOptions();
|
||||
|
||||
String const& directives() const;
|
||||
|
||||
bool hideBody() const;
|
||||
|
||||
Maybe<String> const& techModule() const;
|
||||
|
||||
private:
|
||||
void refreshIconDrawables();
|
||||
void refreshStatusEffects();
|
||||
|
||||
List<String> m_colorOptions;
|
||||
List<PersistentStatusEffect> m_statusEffects;
|
||||
StringSet m_effectSources;
|
||||
String m_directives;
|
||||
bool m_hideBody;
|
||||
Maybe<String> m_techModule;
|
||||
};
|
||||
|
||||
class HeadArmor : public ArmorItem, public PreviewableItem {
|
||||
public:
|
||||
HeadArmor(Json const& config, String const& directory, Json const& data);
|
||||
virtual ~HeadArmor() {}
|
||||
|
||||
virtual ItemPtr clone() const;
|
||||
|
||||
String const& frameset(Gender gender) const;
|
||||
String const& maskDirectives() const;
|
||||
|
||||
virtual List<Drawable> preview(PlayerPtr const& viewer = {}) const;
|
||||
|
||||
private:
|
||||
String m_maleImage;
|
||||
String m_femaleImage;
|
||||
String m_maskDirectives;
|
||||
};
|
||||
|
||||
class ChestArmor : public ArmorItem, public PreviewableItem {
|
||||
public:
|
||||
ChestArmor(Json const& config, String const& directory, Json const& data);
|
||||
virtual ~ChestArmor() {}
|
||||
|
||||
virtual ItemPtr clone() const;
|
||||
|
||||
// Will have :run, :normal, :duck, and :portrait
|
||||
String const& bodyFrameset(Gender gender) const;
|
||||
// Will have :idle[1-5], :duck, :rotation, :walk[1-5], :run[1-5], :jump[1-4],
|
||||
// :fall[1-4]
|
||||
String const& frontSleeveFrameset(Gender gender) const;
|
||||
// Same as FSleeve
|
||||
String const& backSleeveFrameset(Gender gender) const;
|
||||
|
||||
virtual List<Drawable> preview(PlayerPtr const& viewer = {}) const;
|
||||
|
||||
private:
|
||||
String m_maleBodyImage;
|
||||
String m_maleFrontSleeveImage;
|
||||
String m_maleBackSleeveImage;
|
||||
|
||||
String m_femaleBodyImage;
|
||||
String m_femaleFrontSleeveImage;
|
||||
String m_femaleBackSleeveImage;
|
||||
};
|
||||
|
||||
class LegsArmor : public ArmorItem, public PreviewableItem {
|
||||
public:
|
||||
LegsArmor(Json const& config, String const& directory, Json const& data);
|
||||
virtual ~LegsArmor() {}
|
||||
|
||||
virtual ItemPtr clone() const;
|
||||
|
||||
// Will have :idle, :duck, :walk[1-8], :run[1-8], :jump[1-4], :fall[1-4]
|
||||
String const& frameset(Gender gender) const;
|
||||
|
||||
virtual List<Drawable> preview(PlayerPtr const& viewer = {}) const;
|
||||
|
||||
private:
|
||||
String m_maleImage;
|
||||
String m_femaleImage;
|
||||
};
|
||||
|
||||
class BackArmor : public ArmorItem, public PreviewableItem {
|
||||
public:
|
||||
BackArmor(Json const& config, String const& directory, Json const& data);
|
||||
virtual ~BackArmor() {}
|
||||
|
||||
virtual ItemPtr clone() const;
|
||||
|
||||
// Will have :idle, :duck, :walk[1-8], :run[1-8], :jump[1-4], :fall[1-4]
|
||||
String const& frameset(Gender gender) const;
|
||||
|
||||
virtual List<Drawable> preview(PlayerPtr const& viewer = {}) const;
|
||||
|
||||
private:
|
||||
String m_maleImage;
|
||||
String m_femaleImage;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
29
source/game/items/StarAugmentItem.cpp
Normal file
29
source/game/items/StarAugmentItem.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include "StarAugmentItem.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarItemDatabase.hpp"
|
||||
#include "StarLuaComponents.hpp"
|
||||
#include "StarItemLuaBindings.hpp"
|
||||
#include "StarConfigLuaBindings.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
AugmentItem::AugmentItem(Json const& config, String const& directory, Json const& parameters)
|
||||
: Item(config, directory, parameters) {}
|
||||
|
||||
AugmentItem::AugmentItem(AugmentItem const& rhs) : AugmentItem(rhs.config(), rhs.directory(), rhs.parameters()) {}
|
||||
|
||||
ItemPtr AugmentItem::clone() const {
|
||||
return make_shared<AugmentItem>(*this);
|
||||
}
|
||||
|
||||
StringList AugmentItem::augmentScripts() const {
|
||||
return jsonToStringList(instanceValue("scripts")).transformed(bind(&AssetPath::relativeTo, directory(), _1));
|
||||
}
|
||||
|
||||
ItemPtr AugmentItem::applyTo(ItemPtr const item) {
|
||||
return Root::singleton().itemDatabase()->applyAugment(item, this);
|
||||
}
|
||||
|
||||
}
|
27
source/game/items/StarAugmentItem.hpp
Normal file
27
source/game/items/StarAugmentItem.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef STAR_AUGMENT_ITEM_HPP
|
||||
#define STAR_AUGMENT_ITEM_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(AugmentItem);
|
||||
|
||||
class AugmentItem : public Item {
|
||||
public:
|
||||
AugmentItem(Json const& config, String const& directory, Json const& parameters = JsonObject());
|
||||
AugmentItem(AugmentItem const& rhs);
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
StringList augmentScripts() const;
|
||||
|
||||
// Makes no change to the given item if the augment can't be applied.
|
||||
// Consumes itself and returns true if the augment is applied.
|
||||
// Has no effect if augmentation fails.
|
||||
ItemPtr applyTo(ItemPtr const item);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
54
source/game/items/StarBlueprintItem.cpp
Normal file
54
source/game/items/StarBlueprintItem.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#include "StarBlueprintItem.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarPlayer.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarPlayerBlueprints.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
BlueprintItem::BlueprintItem(Json const& config, String const& directory, Json const& data)
|
||||
: Item(config, directory, data), SwingableItem(config) {
|
||||
setWindupTime(0.2f);
|
||||
setCooldownTime(0.1f);
|
||||
setMaxStack(1);
|
||||
m_requireEdgeTrigger = true;
|
||||
m_recipe = ItemDescriptor(instanceValue("recipe"));
|
||||
|
||||
m_recipeIconUnderlay = Drawable(Root::singleton().assets()->json("/blueprint.config:iconUnderlay"));
|
||||
m_inHandDrawable = {Drawable::makeImage(
|
||||
Root::singleton().assets()->json("/blueprint.config:inHandImage").toString(),
|
||||
1.0f / TilePixels,
|
||||
true,
|
||||
Vec2F())};
|
||||
|
||||
setPrice(int(price() * Root::singleton().assets()->json("/items/defaultParameters.config:blueprintPriceFactor").toFloat()));
|
||||
}
|
||||
|
||||
ItemPtr BlueprintItem::clone() const {
|
||||
return make_shared<BlueprintItem>(*this);
|
||||
}
|
||||
|
||||
List<Drawable> BlueprintItem::drawables() const {
|
||||
return m_inHandDrawable;
|
||||
}
|
||||
|
||||
void BlueprintItem::fireTriggered() {
|
||||
if (count())
|
||||
if (auto player = as<Player>(owner()))
|
||||
if (player->addBlueprint(m_recipe, true))
|
||||
setCount(count() - 1);
|
||||
}
|
||||
|
||||
List<Drawable> BlueprintItem::iconDrawables() const {
|
||||
List<Drawable> result;
|
||||
result.append(m_recipeIconUnderlay);
|
||||
result.appendAll(Item::iconDrawables());
|
||||
return result;
|
||||
}
|
||||
|
||||
List<Drawable> BlueprintItem::dropDrawables() const {
|
||||
return m_inHandDrawable;
|
||||
}
|
||||
|
||||
}
|
32
source/game/items/StarBlueprintItem.hpp
Normal file
32
source/game/items/StarBlueprintItem.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef STAR_BLUEPRINT_ITEM_HPP
|
||||
#define STAR_BLUEPRINT_ITEM_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
#include "StarWorld.hpp"
|
||||
#include "StarSwingableItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(BlueprintItem);
|
||||
|
||||
class BlueprintItem : public Item, public SwingableItem {
|
||||
public:
|
||||
BlueprintItem(Json const& config, String const& directory, Json const& data);
|
||||
virtual ItemPtr clone() const override;
|
||||
|
||||
virtual List<Drawable> drawables() const override;
|
||||
|
||||
virtual void fireTriggered() override;
|
||||
|
||||
virtual List<Drawable> iconDrawables() const override;
|
||||
virtual List<Drawable> dropDrawables() const override;
|
||||
|
||||
private:
|
||||
ItemDescriptor m_recipe;
|
||||
Drawable m_recipeIconUnderlay;
|
||||
List<Drawable> m_inHandDrawable;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
49
source/game/items/StarCodexItem.cpp
Normal file
49
source/game/items/StarCodexItem.cpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#include "StarCodexItem.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarPlayer.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarClientContext.hpp"
|
||||
#include "StarCodex.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
CodexItem::CodexItem(Json const& config, String const& directory, Json const& data)
|
||||
: Item(config, directory, data), SwingableItem(config) {
|
||||
setWindupTime(0.2f);
|
||||
setCooldownTime(0.5f);
|
||||
m_requireEdgeTrigger = true;
|
||||
m_codexId = instanceValue("codexId").toString();
|
||||
String iconPath = instanceValue("codexIcon").toString();
|
||||
m_iconDrawables = {Drawable::makeImage(iconPath, 1.0f, true, Vec2F())};
|
||||
m_worldDrawables = {Drawable::makeImage(iconPath, 1.0f / TilePixels, true, Vec2F())};
|
||||
}
|
||||
|
||||
ItemPtr CodexItem::clone() const {
|
||||
return make_shared<CodexItem>(*this);
|
||||
}
|
||||
|
||||
List<Drawable> CodexItem::drawables() const {
|
||||
return m_worldDrawables;
|
||||
}
|
||||
|
||||
void CodexItem::fireTriggered() {
|
||||
if (auto player = as<Player>(owner())) {
|
||||
auto codexLearned = player->codexes()->learnCodex(m_codexId);
|
||||
if (codexLearned) {
|
||||
player->queueUIMessage(Root::singleton().assets()->json("/codex.config:messages.learned").toString());
|
||||
} else {
|
||||
player->queueUIMessage(Root::singleton().assets()->json("/codex.config:messages.alreadyKnown").toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Drawable> CodexItem::iconDrawables() const {
|
||||
return m_iconDrawables;
|
||||
}
|
||||
|
||||
List<Drawable> CodexItem::dropDrawables() const {
|
||||
return m_worldDrawables;
|
||||
}
|
||||
|
||||
}
|
30
source/game/items/StarCodexItem.hpp
Normal file
30
source/game/items/StarCodexItem.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#ifndef STAR_CODEX_ITEM_HPP
|
||||
#define STAR_CODEX_ITEM_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
#include "StarPlayerCodexes.hpp"
|
||||
#include "StarSwingableItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
class CodexItem : public Item, public SwingableItem {
|
||||
public:
|
||||
CodexItem(Json const& config, String const& directory, Json const& data);
|
||||
virtual ItemPtr clone() const override;
|
||||
|
||||
virtual List<Drawable> drawables() const override;
|
||||
|
||||
virtual void fireTriggered() override;
|
||||
|
||||
virtual List<Drawable> iconDrawables() const override;
|
||||
virtual List<Drawable> dropDrawables() const override;
|
||||
|
||||
private:
|
||||
String m_codexId;
|
||||
List<Drawable> m_iconDrawables;
|
||||
List<Drawable> m_worldDrawables;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
110
source/game/items/StarConsumableItem.cpp
Normal file
110
source/game/items/StarConsumableItem.cpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
#include "StarConsumableItem.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarRandom.hpp"
|
||||
#include "StarStatusController.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ConsumableItem::ConsumableItem(Json const& config, String const& directory, Json const& data)
|
||||
: Item(config, directory, data), SwingableItem(config) {
|
||||
setWindupTime(0);
|
||||
setCooldownTime(0.25f);
|
||||
m_requireEdgeTrigger = true;
|
||||
m_swingStart = config.getFloat("swingStart", -60) * Constants::pi / 180;
|
||||
m_swingFinish = config.getFloat("swingFinish", 40) * Constants::pi / 180;
|
||||
m_swingAimFactor = config.getFloat("swingAimFactor", 0.2f);
|
||||
m_blockingEffects = jsonToStringSet(instanceValue("blockingEffects", JsonArray()));
|
||||
if (auto foodValue = instanceValue("foodValue")) {
|
||||
m_foodValue = foodValue.toFloat();
|
||||
m_blockingEffects.add("wellfed");
|
||||
}
|
||||
m_emitters = jsonToStringSet(instanceValue("emitters", JsonArray{"eating"}));
|
||||
m_emote = instanceValue("emote", "eat").toString();
|
||||
m_consuming = false;
|
||||
}
|
||||
|
||||
ItemPtr ConsumableItem::clone() const {
|
||||
return make_shared<ConsumableItem>(*this);
|
||||
}
|
||||
|
||||
List<Drawable> ConsumableItem::drawables() const {
|
||||
auto drawables = iconDrawables();
|
||||
Drawable::scaleAll(drawables, 1.0f / TilePixels);
|
||||
Drawable::translateAll(drawables, -handPosition() / TilePixels);
|
||||
return drawables;
|
||||
}
|
||||
|
||||
void ConsumableItem::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
|
||||
SwingableItem::update(fireMode, shifting, moves);
|
||||
|
||||
if (entityMode() == EntityMode::Master) {
|
||||
if (m_consuming)
|
||||
owner()->addEffectEmitters(m_emitters);
|
||||
if (ready())
|
||||
maybeConsume();
|
||||
}
|
||||
}
|
||||
|
||||
void ConsumableItem::fire(FireMode mode, bool shifting, bool edgeTriggered) {
|
||||
if (canUse())
|
||||
FireableItem::fire(mode, shifting, edgeTriggered);
|
||||
}
|
||||
|
||||
void ConsumableItem::fireTriggered() {
|
||||
if (canUse()) {
|
||||
triggerEffects();
|
||||
FireableItem::fireTriggered();
|
||||
}
|
||||
}
|
||||
|
||||
void ConsumableItem::uninit() {
|
||||
maybeConsume();
|
||||
FireableItem::uninit();
|
||||
}
|
||||
|
||||
bool ConsumableItem::canUse() const {
|
||||
if (!count() || m_consuming)
|
||||
return false;
|
||||
|
||||
for (auto pair : owner()->statusController()->activeUniqueStatusEffectSummary()) {
|
||||
if (m_blockingEffects.contains(pair.first))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConsumableItem::triggerEffects() {
|
||||
auto options = instanceValue("effects", JsonArray()).toArray();
|
||||
if (options.size()) {
|
||||
auto option = Random::randFrom(options).toArray().transformed(jsonToEphemeralStatusEffect);
|
||||
owner()->statusController()->addEphemeralEffects(option);
|
||||
}
|
||||
|
||||
if (m_foodValue) {
|
||||
owner()->statusController()->giveResource("food", *m_foodValue);
|
||||
if (owner()->statusController()->resourcePercentage("food") == 1.0f)
|
||||
owner()->statusController()->addEphemeralEffect(EphemeralStatusEffect{UniqueStatusEffect("wellfed"), {}});
|
||||
}
|
||||
|
||||
if (!m_emote.empty())
|
||||
owner()->requestEmote(m_emote);
|
||||
|
||||
m_consuming = true;
|
||||
}
|
||||
|
||||
void ConsumableItem::maybeConsume() {
|
||||
if (m_consuming) {
|
||||
m_consuming = false;
|
||||
|
||||
world()->sendEntityMessage(owner()->entityId(), "recordEvent", {"useItem", JsonObject {
|
||||
{"itemType", name()}
|
||||
}});
|
||||
if (count())
|
||||
setCount(count() - 1);
|
||||
else
|
||||
setCount(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
38
source/game/items/StarConsumableItem.hpp
Normal file
38
source/game/items/StarConsumableItem.hpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef STAR_CONSUMABLE_ITEM_HPP
|
||||
#define STAR_CONSUMABLE_ITEM_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
#include "StarGameTypes.hpp"
|
||||
#include "StarSwingableItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
class ConsumableItem : public Item, public SwingableItem {
|
||||
public:
|
||||
ConsumableItem(Json const& config, String const& directory, Json const& data);
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
void fire(FireMode mode, bool shifting, bool edgeTriggered) override;
|
||||
void fireTriggered() override;
|
||||
void uninit() override;
|
||||
|
||||
private:
|
||||
bool canUse() const;
|
||||
|
||||
void triggerEffects();
|
||||
void maybeConsume();
|
||||
|
||||
StringSet m_blockingEffects;
|
||||
Maybe<float> m_foodValue;
|
||||
StringSet m_emitters;
|
||||
String m_emote;
|
||||
bool m_consuming;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
42
source/game/items/StarCurrency.cpp
Normal file
42
source/game/items/StarCurrency.cpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
#include "StarCurrency.hpp"
|
||||
#include "StarRandom.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
CurrencyItem::CurrencyItem(Json const& config, String const& directory) : Item(config, directory) {
|
||||
m_currency = config.getString("currency");
|
||||
m_value = config.getUInt("value");
|
||||
}
|
||||
|
||||
ItemPtr CurrencyItem::clone() const {
|
||||
return make_shared<CurrencyItem>(*this);
|
||||
}
|
||||
|
||||
String CurrencyItem::pickupSound() const {
|
||||
if (count() <= instanceValue("smallStackLimit", 100).toUInt()) {
|
||||
if (!instanceValue("pickupSoundsSmall", {}).isNull())
|
||||
return Random::randFrom(jsonToStringSet(instanceValue("pickupSoundsSmall")));
|
||||
} else if (count() <= instanceValue("mediumStackLimit", 10000).toUInt()) {
|
||||
if (!instanceValue("pickupSoundsMedium", {}).isNull())
|
||||
return Random::randFrom(jsonToStringSet(instanceValue("pickupSoundsMedium")));
|
||||
} else {
|
||||
if (!instanceValue("pickupSoundsLarge", {}).isNull())
|
||||
return Random::randFrom(jsonToStringSet(instanceValue("pickupSoundsLarge")));
|
||||
}
|
||||
return Item::pickupSound();
|
||||
}
|
||||
|
||||
String CurrencyItem::currencyType() {
|
||||
return m_currency;
|
||||
}
|
||||
|
||||
uint64_t CurrencyItem::currencyValue() {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
uint64_t CurrencyItem::totalValue() {
|
||||
return m_value * count();
|
||||
}
|
||||
|
||||
}
|
33
source/game/items/StarCurrency.hpp
Normal file
33
source/game/items/StarCurrency.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef STAR_CURRENCY_HPP
|
||||
#define STAR_CURRENCY_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(CurrencyItem);
|
||||
|
||||
class CurrencyItem : public Item {
|
||||
public:
|
||||
CurrencyItem(Json const& config, String const& directory);
|
||||
|
||||
virtual ItemPtr clone() const override;
|
||||
|
||||
virtual String pickupSound() const override;
|
||||
|
||||
String currencyType();
|
||||
|
||||
// Value of a single instance of this currency
|
||||
uint64_t currencyValue();
|
||||
|
||||
// Total value of all currencies (so currencyValue * count)
|
||||
uint64_t totalValue();
|
||||
|
||||
private:
|
||||
String m_currency;
|
||||
uint64_t m_value;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
173
source/game/items/StarInspectionTool.cpp
Normal file
173
source/game/items/StarInspectionTool.cpp
Normal file
|
@ -0,0 +1,173 @@
|
|||
#include "StarInspectionTool.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarMaterialDatabase.hpp"
|
||||
#include "StarLiquidsDatabase.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
InspectionTool::InspectionTool(Json const& config, String const& directory, Json const& parameters)
|
||||
: Item(config, directory, parameters) {
|
||||
m_image = AssetPath::relativeTo(directory, instanceValue("image").toString());
|
||||
m_handPosition = jsonToVec2F(instanceValue("handPosition"));
|
||||
m_lightPosition = jsonToVec2F(instanceValue("lightPosition"));
|
||||
m_lightColor = jsonToColor(instanceValue("lightColor"));
|
||||
m_beamWidth = instanceValue("beamLevel").toFloat();
|
||||
m_ambientFactor = instanceValue("beamAmbience").toFloat();
|
||||
|
||||
m_showHighlights = instanceValue("showHighlights").toBool();
|
||||
m_allowScanning = instanceValue("allowScanning").toBool();
|
||||
|
||||
m_inspectionAngles = jsonToVec2F(instanceValue("inspectionAngles"));
|
||||
m_inspectionRanges = jsonToVec2F(instanceValue("inspectionRanges"));
|
||||
m_ambientInspectionRadius = instanceValue("ambientInspectionRadius").toFloat();
|
||||
m_fullInspectionSpaces = instanceValue("fullInspectionSpaces").toUInt();
|
||||
m_minimumInspectionLevel = instanceValue("minimumInspectionLevel").toFloat();
|
||||
|
||||
m_lastFireMode = FireMode::None;
|
||||
}
|
||||
|
||||
ItemPtr InspectionTool::clone() const {
|
||||
return make_shared<InspectionTool>(*this);
|
||||
}
|
||||
|
||||
void InspectionTool::update(FireMode fireMode, bool, HashSet<MoveControlType> const&) {
|
||||
m_currentAngle = world()->geometry().diff(owner()->aimPosition(), owner()->position()).angle();
|
||||
m_currentPosition = owner()->position() + owner()->handPosition(hand(), m_lightPosition - m_handPosition);
|
||||
SpatialLogger::logPoint("world", m_currentPosition, {0, 0, 255, 255});
|
||||
|
||||
if (fireMode != m_lastFireMode) {
|
||||
if (fireMode != FireMode::None)
|
||||
m_inspectionResults.append(inspect(owner()->aimPosition()));
|
||||
}
|
||||
|
||||
m_lastFireMode = fireMode;
|
||||
}
|
||||
|
||||
List<Drawable> InspectionTool::drawables() const {
|
||||
return {Drawable::makeImage(m_image, 1.0f / TilePixels, true, -m_handPosition)};
|
||||
}
|
||||
|
||||
List<LightSource> InspectionTool::lightSources() const {
|
||||
if (!initialized())
|
||||
return {};
|
||||
|
||||
float angle = world()->geometry().diff(owner()->aimPosition(), owner()->position()).angle();
|
||||
LightSource lightSource;
|
||||
lightSource.pointLight = true;
|
||||
lightSource.position = owner()->position() + owner()->handPosition(hand(), m_lightPosition - m_handPosition);
|
||||
lightSource.color = m_lightColor.toRgb();
|
||||
lightSource.pointBeam = m_beamWidth;
|
||||
lightSource.beamAngle = angle;
|
||||
lightSource.beamAmbience = m_ambientFactor;
|
||||
return {move(lightSource)};
|
||||
}
|
||||
|
||||
float InspectionTool::inspectionHighlightLevel(InspectableEntityPtr const& inspectable) const {
|
||||
if (m_showHighlights)
|
||||
return inspectionLevel(inspectable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
List<InspectionTool::InspectionResult> InspectionTool::pullInspectionResults() {
|
||||
return Star::take(m_inspectionResults);
|
||||
}
|
||||
|
||||
float InspectionTool::inspectionLevel(InspectableEntityPtr const& inspectable) const {
|
||||
if (!initialized() || !inspectable->inspectable())
|
||||
return 0;
|
||||
|
||||
float totalLevel = 0;
|
||||
|
||||
// convert spaces to a set of world positions
|
||||
Set<Vec2I> spaceSet;
|
||||
for (auto space : inspectable->spaces())
|
||||
spaceSet.add(inspectable->tilePosition() + space);
|
||||
|
||||
for (auto space : spaceSet) {
|
||||
float pointLevel = pointInspectionLevel(centerOfTile(space));
|
||||
if (pointLevel > 0 && hasLineOfSight(space, spaceSet))
|
||||
totalLevel += pointLevel;
|
||||
}
|
||||
return clamp(totalLevel / min(spaceSet.size(), m_fullInspectionSpaces), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
float InspectionTool::pointInspectionLevel(Vec2F const& position) const {
|
||||
Vec2F gdiff = world()->geometry().diff(position, m_currentPosition);
|
||||
float gdist = gdiff.magnitude();
|
||||
float angleFactor = (abs(angleDiff(gdiff.angle(), m_currentAngle)) - m_inspectionAngles[0]) / (m_inspectionAngles[1] - m_inspectionAngles[0]);
|
||||
float distFactor = (gdist - m_inspectionRanges[0]) / (m_inspectionRanges[1] - m_inspectionRanges[0]);
|
||||
float ambientFactor = gdist / m_ambientInspectionRadius;
|
||||
return 1 - clamp(max(distFactor, min(ambientFactor, angleFactor)), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
bool InspectionTool::hasLineOfSight(Vec2I const& position, Set<Vec2I> const& targetSpaces) const {
|
||||
auto collisions = world()->collidingTilesAlongLine(centerOfTile(m_currentPosition), centerOfTile(position));
|
||||
for (auto collision : collisions) {
|
||||
if (collision != position && !targetSpaces.contains(collision))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
InspectionTool::InspectionResult InspectionTool::inspect(Vec2F const& position) {
|
||||
auto assets = Root::singleton().assets();
|
||||
auto species = owner()->species();
|
||||
|
||||
// if there's a candidate InspectableEntity at the position, make sure that entity's total inspection level
|
||||
// is above the minimum threshold
|
||||
for (auto entity : world()->atTile<InspectableEntity>(Vec2I::floor(position))) {
|
||||
if (entity->inspectable() && inspectionLevel(entity) >= m_minimumInspectionLevel) {
|
||||
if (m_allowScanning)
|
||||
return {entity->inspectionDescription(species).value(), entity->inspectionLogName(), entity->entityId()};
|
||||
else
|
||||
return {entity->inspectionDescription(species).value(), {}, {}};
|
||||
}
|
||||
}
|
||||
|
||||
// check the inspection level at the selected tile
|
||||
if (!hasLineOfSight(Vec2I::floor(position)) || pointInspectionLevel(centerOfTile(position)) < m_minimumInspectionLevel)
|
||||
return {inspectionFailureText("outOfRangeText", species), {}};
|
||||
|
||||
// check the tile for foreground mod or material
|
||||
MaterialId fgMaterial = world()->material(Vec2I::floor(position), TileLayer::Foreground);
|
||||
MaterialId fgMod = world()->mod(Vec2I(position.floor()), TileLayer::Foreground);
|
||||
auto materialDatabase = Root::singleton().materialDatabase();
|
||||
if (isRealMaterial(fgMaterial)) {
|
||||
if (isRealMod(fgMod))
|
||||
return {materialDatabase->modDescription(fgMod, species), {}};
|
||||
else
|
||||
return {materialDatabase->materialDescription(fgMaterial, species), {}};
|
||||
}
|
||||
|
||||
// check for liquid at the tile
|
||||
auto liquidLevel = world()->liquidLevel(Vec2I::floor(position));
|
||||
auto liquidsDatabase = Root::singleton().liquidsDatabase();
|
||||
if (liquidLevel.liquid != EmptyLiquidId)
|
||||
return {liquidsDatabase->liquidDescription(liquidLevel.liquid), {}};
|
||||
|
||||
// check the tile for background mod or material
|
||||
MaterialId bgMaterial = world()->material(Vec2I::floor(position), TileLayer::Background);
|
||||
MaterialId bgMod = world()->mod(Vec2I(position.floor()), TileLayer::Background);
|
||||
if (isRealMaterial(bgMaterial)) {
|
||||
if (isRealMod(bgMod))
|
||||
return {materialDatabase->modDescription(bgMod, species), {}};
|
||||
else
|
||||
return {materialDatabase->materialDescription(bgMaterial, species), {}};
|
||||
}
|
||||
|
||||
// at this point you're just staring into the void
|
||||
return {inspectionFailureText("nothingThereText", species), {}};
|
||||
}
|
||||
|
||||
String InspectionTool::inspectionFailureText(String const& failureType, String const& species) const {
|
||||
JsonArray textOptions;
|
||||
Json nothingThere = instanceValue(failureType);
|
||||
if (nothingThere.contains(species))
|
||||
textOptions = nothingThere.getArray(species);
|
||||
else
|
||||
textOptions = nothingThere.getArray("default");
|
||||
return textOptions.wrap(Random::randu64()).toString();
|
||||
}
|
||||
|
||||
}
|
74
source/game/items/StarInspectionTool.hpp
Normal file
74
source/game/items/StarInspectionTool.hpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#ifndef STAR_INSPECTION_TOOL_HPP
|
||||
#define STAR_INSPECTION_TOOL_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
#include "StarPointableItem.hpp"
|
||||
#include "StarToolUserItem.hpp"
|
||||
#include "StarEntityRendering.hpp"
|
||||
#include "StarInspectableEntity.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(InspectionTool);
|
||||
|
||||
class InspectionTool
|
||||
: public Item,
|
||||
public PointableItem,
|
||||
public ToolUserItem {
|
||||
public:
|
||||
|
||||
struct InspectionResult {
|
||||
String message;
|
||||
Maybe<String> objectName;
|
||||
Maybe<EntityId> entityId;
|
||||
};
|
||||
|
||||
InspectionTool(Json const& config, String const& directory, Json const& parameters = JsonObject());
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
|
||||
List<LightSource> lightSources() const;
|
||||
|
||||
float inspectionHighlightLevel(InspectableEntityPtr const& inspectableEntity) const;
|
||||
|
||||
List<InspectionResult> pullInspectionResults();
|
||||
|
||||
private:
|
||||
InspectionResult inspect(Vec2F const& position);
|
||||
|
||||
float inspectionLevel(InspectableEntityPtr const& inspectableEntity) const;
|
||||
float pointInspectionLevel(Vec2F const& position) const;
|
||||
bool hasLineOfSight(Vec2I const& targetPosition, Set<Vec2I> const& targetSpaces = {}) const;
|
||||
|
||||
String inspectionFailureText(String const& failureType, String const& species) const;
|
||||
|
||||
float m_currentAngle;
|
||||
Vec2F m_currentPosition;
|
||||
|
||||
String m_image;
|
||||
Vec2F m_handPosition;
|
||||
Vec2F m_lightPosition;
|
||||
Color m_lightColor;
|
||||
float m_beamWidth;
|
||||
float m_ambientFactor;
|
||||
|
||||
bool m_showHighlights;
|
||||
bool m_allowScanning;
|
||||
|
||||
Vec2F m_inspectionAngles;
|
||||
Vec2F m_inspectionRanges;
|
||||
float m_ambientInspectionRadius;
|
||||
size_t m_fullInspectionSpaces;
|
||||
float m_minimumInspectionLevel;
|
||||
|
||||
FireMode m_lastFireMode;
|
||||
List<InspectionResult> m_inspectionResults;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
88
source/game/items/StarInstrumentItem.cpp
Normal file
88
source/game/items/StarInstrumentItem.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
#include "StarInstrumentItem.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
InstrumentItem::InstrumentItem(Json const& config, String const& directory, Json const& data) : Item(config, directory, data) {
|
||||
m_activeCooldown = 0;
|
||||
|
||||
auto image = AssetPath::relativeTo(directory, instanceValue("image").toString());
|
||||
Vec2F position = jsonToVec2F(instanceValue("handPosition", JsonArray{0, 0}));
|
||||
m_drawables.append(Drawable::makeImage(image, 1.0f / TilePixels, true, position));
|
||||
|
||||
image = AssetPath::relativeTo(directory, instanceValue("activeImage").toString());
|
||||
position = jsonToVec2F(instanceValue("activeHandPosition", JsonArray{0, 0}));
|
||||
m_activeDrawables.append(Drawable::makeImage(image, 1.0f / TilePixels, true, position));
|
||||
|
||||
m_activeAngle = (instanceValue("activeAngle").toFloat() / 180.0f) * Constants::pi;
|
||||
|
||||
m_activeStatusEffects = instanceValue("activeStatusEffects", JsonArray()).toArray().transformed(jsonToPersistentStatusEffect);
|
||||
m_inactiveStatusEffects = instanceValue("inactiveStatusEffects", JsonArray()).toArray().transformed(jsonToPersistentStatusEffect);
|
||||
m_activeEffectSources = jsonToStringSet(instanceValue("activeEffectSources", JsonArray()));
|
||||
m_inactiveEffectSources = jsonToStringSet(instanceValue("inactiveEffectSources", JsonArray()));
|
||||
|
||||
m_kind = instanceValue("kind").toString();
|
||||
}
|
||||
|
||||
ItemPtr InstrumentItem::clone() const {
|
||||
return make_shared<InstrumentItem>(*this);
|
||||
}
|
||||
|
||||
List<PersistentStatusEffect> InstrumentItem::statusEffects() const {
|
||||
if (active())
|
||||
return m_activeStatusEffects;
|
||||
return m_inactiveStatusEffects;
|
||||
}
|
||||
|
||||
StringSet InstrumentItem::effectSources() const {
|
||||
if (active())
|
||||
return m_activeEffectSources;
|
||||
return m_inactiveEffectSources;
|
||||
}
|
||||
|
||||
void InstrumentItem::update(FireMode, bool, HashSet<MoveControlType> const&) {
|
||||
if (entityMode() == EntityMode::Master) {
|
||||
if (active()) {
|
||||
m_activeCooldown--;
|
||||
owner()->addEffectEmitters({"music"});
|
||||
}
|
||||
}
|
||||
owner()->instrumentEquipped(m_kind);
|
||||
}
|
||||
|
||||
bool InstrumentItem::active() const {
|
||||
if (!initialized())
|
||||
return false;
|
||||
return (m_activeCooldown > 0) || owner()->instrumentPlaying();
|
||||
}
|
||||
|
||||
void InstrumentItem::setActive(bool active) {
|
||||
if (active)
|
||||
m_activeCooldown = 3;
|
||||
else
|
||||
m_activeCooldown = 0;
|
||||
}
|
||||
|
||||
bool InstrumentItem::usable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstrumentItem::activate() {
|
||||
owner()->interact(InteractAction{InteractActionType::OpenSongbookInterface, owner()->entityId(), {}});
|
||||
}
|
||||
|
||||
List<Drawable> InstrumentItem::drawables() const {
|
||||
if (active())
|
||||
return m_activeDrawables;
|
||||
return m_drawables;
|
||||
}
|
||||
|
||||
float InstrumentItem::getAngle(float angle) {
|
||||
if (active())
|
||||
return m_activeAngle;
|
||||
return angle;
|
||||
}
|
||||
|
||||
}
|
57
source/game/items/StarInstrumentItem.hpp
Normal file
57
source/game/items/StarInstrumentItem.hpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#ifndef STAR_INSTRUMENT_ITEM_HPP
|
||||
#define STAR_INSTRUMENT_ITEM_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
#include "StarInstrumentItem.hpp"
|
||||
#include "StarStatusEffectItem.hpp"
|
||||
#include "StarEffectSourceItem.hpp"
|
||||
#include "StarToolUserItem.hpp"
|
||||
#include "StarActivatableItem.hpp"
|
||||
#include "StarPointableItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(World);
|
||||
STAR_CLASS(ToolUserEntity);
|
||||
STAR_CLASS(InstrumentItem);
|
||||
|
||||
class InstrumentItem : public Item,
|
||||
public StatusEffectItem,
|
||||
public EffectSourceItem,
|
||||
public ToolUserItem,
|
||||
public ActivatableItem,
|
||||
public PointableItem {
|
||||
public:
|
||||
InstrumentItem(Json const& config, String const& directory, Json const& data);
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
List<PersistentStatusEffect> statusEffects() const override;
|
||||
StringSet effectSources() const override;
|
||||
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
|
||||
bool active() const override;
|
||||
void setActive(bool active) override;
|
||||
bool usable() const override;
|
||||
void activate() override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
float getAngle(float angle) override;
|
||||
|
||||
private:
|
||||
List<PersistentStatusEffect> m_activeStatusEffects;
|
||||
List<PersistentStatusEffect> m_inactiveStatusEffects;
|
||||
StringSet m_activeEffectSources;
|
||||
StringSet m_inactiveEffectSources;
|
||||
List<Drawable> m_drawables;
|
||||
List<Drawable> m_activeDrawables;
|
||||
int m_activeCooldown;
|
||||
|
||||
float m_activeAngle;
|
||||
String m_kind;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
151
source/game/items/StarLiquidItem.cpp
Normal file
151
source/game/items/StarLiquidItem.cpp
Normal file
|
@ -0,0 +1,151 @@
|
|||
#include "StarLiquidItem.hpp"
|
||||
#include "StarJson.hpp"
|
||||
#include "StarLiquidsDatabase.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarWorld.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
LiquidItem::LiquidItem(Json const& config, String const& directory, Json const& settings)
|
||||
: Item(config, directory, settings), FireableItem(config), BeamItem(config) {
|
||||
m_liquidId = Root::singleton().liquidsDatabase()->liquidId(config.getString("liquid"));
|
||||
|
||||
setTwoHanded(config.getBool("twoHanded", true));
|
||||
|
||||
auto assets = Root::singleton().assets();
|
||||
m_quantity = assets->json("/items/defaultParameters.config:liquidItems.bucketSize").toUInt();
|
||||
setCooldownTime(assets->json("/items/defaultParameters.config:liquidItems.cooldown").toFloat());
|
||||
m_blockRadius = assets->json("/items/defaultParameters.config:blockRadius").toFloat();
|
||||
m_altBlockRadius = assets->json("/items/defaultParameters.config:altBlockRadius").toFloat();
|
||||
m_shifting = false;
|
||||
}
|
||||
|
||||
ItemPtr LiquidItem::clone() const {
|
||||
return make_shared<LiquidItem>(*this);
|
||||
}
|
||||
|
||||
void LiquidItem::init(ToolUserEntity* owner, ToolHand hand) {
|
||||
FireableItem::init(owner, hand);
|
||||
BeamItem::init(owner, hand);
|
||||
}
|
||||
|
||||
void LiquidItem::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
|
||||
FireableItem::update(fireMode, shifting, moves);
|
||||
BeamItem::update(fireMode, shifting, moves);
|
||||
if (shifting || !multiplaceEnabled())
|
||||
setEnd(BeamItem::EndType::Tile);
|
||||
else
|
||||
setEnd(BeamItem::EndType::TileGroup);
|
||||
|
||||
m_shifting = shifting;
|
||||
}
|
||||
|
||||
List<Drawable> LiquidItem::nonRotatedDrawables() const {
|
||||
return beamDrawables(canPlace(m_shifting));
|
||||
}
|
||||
|
||||
void LiquidItem::fire(FireMode mode, bool shifting, bool edgeTriggered) {
|
||||
if (!initialized() || !ready() || !owner()->inToolRange())
|
||||
return;
|
||||
|
||||
PlaceLiquid placeLiquid{liquidId(), liquidQuantity()};
|
||||
TileModificationList modifications;
|
||||
|
||||
float radius;
|
||||
|
||||
if (!shifting)
|
||||
radius = m_blockRadius;
|
||||
else
|
||||
radius = m_altBlockRadius;
|
||||
|
||||
if (!multiplaceEnabled())
|
||||
radius = 1;
|
||||
|
||||
for (auto pos : tileAreaBrush(radius, owner()->aimPosition(), true)) {
|
||||
if (canPlaceAtTile(pos))
|
||||
modifications.append({pos, placeLiquid});
|
||||
}
|
||||
|
||||
// Make sure not to make any more modifications than we have consumables.
|
||||
if (modifications.size() > count())
|
||||
modifications.resize(count());
|
||||
size_t failed = world()->applyTileModifications(modifications, false).size();
|
||||
if (failed < modifications.size()) {
|
||||
FireableItem::fire(mode, shifting, edgeTriggered);
|
||||
consume(modifications.size() - failed);
|
||||
}
|
||||
}
|
||||
|
||||
LiquidId LiquidItem::liquidId() const {
|
||||
return m_liquidId;
|
||||
}
|
||||
|
||||
float LiquidItem::liquidQuantity() const {
|
||||
return m_quantity;
|
||||
}
|
||||
|
||||
List<PreviewTile> LiquidItem::preview(bool shifting) const {
|
||||
List<PreviewTile> result;
|
||||
if (initialized()) {
|
||||
auto liquid = liquidId();
|
||||
|
||||
float radius;
|
||||
if (!shifting)
|
||||
radius = m_blockRadius;
|
||||
else
|
||||
radius = m_altBlockRadius;
|
||||
|
||||
if (!multiplaceEnabled())
|
||||
radius = 1;
|
||||
|
||||
size_t c = 0;
|
||||
|
||||
for (auto pos : tileAreaBrush(radius, owner()->aimPosition(), true)) {
|
||||
if (c >= count())
|
||||
break;
|
||||
if (canPlaceAtTile(pos))
|
||||
c++;
|
||||
result.append({pos, liquid});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LiquidItem::canPlace(bool shifting) const {
|
||||
if (initialized()) {
|
||||
float radius;
|
||||
if (!shifting)
|
||||
radius = m_blockRadius;
|
||||
else
|
||||
radius = m_altBlockRadius;
|
||||
|
||||
if (!multiplaceEnabled())
|
||||
radius = 1;
|
||||
|
||||
for (auto pos : tileAreaBrush(radius, owner()->aimPosition(), true)) {
|
||||
if (canPlaceAtTile(pos))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LiquidItem::canPlaceAtTile(Vec2I pos) const {
|
||||
auto bgTileMaterial = world()->material(pos, TileLayer::Background);
|
||||
if (bgTileMaterial != EmptyMaterialId) {
|
||||
auto fgTileMaterial = world()->material(pos, TileLayer::Foreground);
|
||||
if (fgTileMaterial == EmptyMaterialId) {
|
||||
auto tileLiquid = world()->liquidLevel(pos).liquid;
|
||||
if (tileLiquid == EmptyLiquidId || tileLiquid == liquidId())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LiquidItem::multiplaceEnabled() const {
|
||||
return (count() > 1);
|
||||
}
|
||||
|
||||
}
|
47
source/game/items/StarLiquidItem.hpp
Normal file
47
source/game/items/StarLiquidItem.hpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#ifndef STAR_LIQUID_ITEM_HPP
|
||||
#define STAR_LIQUID_ITEM_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
#include "StarFireableItem.hpp"
|
||||
#include "StarBeamItem.hpp"
|
||||
#include "StarEntityRendering.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(LiquidItem);
|
||||
|
||||
class LiquidItem : public Item, public FireableItem, public BeamItem {
|
||||
public:
|
||||
LiquidItem(Json const& config, String const& directory, Json const& settings);
|
||||
virtual ~LiquidItem() {}
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
void init(ToolUserEntity* owner, ToolHand hand) override;
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
|
||||
List<Drawable> nonRotatedDrawables() const override;
|
||||
|
||||
void fire(FireMode mode, bool shifting, bool edgeTriggered) override;
|
||||
|
||||
LiquidId liquidId() const;
|
||||
float liquidQuantity() const;
|
||||
|
||||
List<PreviewTile> preview(bool shifting) const;
|
||||
|
||||
bool canPlace(bool shifting) const;
|
||||
bool canPlaceAtTile(Vec2I pos) const;
|
||||
bool multiplaceEnabled() const;
|
||||
|
||||
private:
|
||||
LiquidId m_liquidId;
|
||||
float m_quantity;
|
||||
|
||||
float m_blockRadius;
|
||||
float m_altBlockRadius;
|
||||
bool m_shifting;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
175
source/game/items/StarMaterialItem.cpp
Normal file
175
source/game/items/StarMaterialItem.cpp
Normal file
|
@ -0,0 +1,175 @@
|
|||
#include "StarMaterialItem.hpp"
|
||||
#include "StarJson.hpp"
|
||||
#include "StarMaterialDatabase.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarWorld.hpp"
|
||||
#include "StarWorldClient.hpp"
|
||||
#include "StarWorldTemplate.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
MaterialItem::MaterialItem(Json const& config, String const& directory, Json const& settings)
|
||||
: Item(config, directory, settings), FireableItem(config), BeamItem(config) {
|
||||
m_material = config.getInt("materialId");
|
||||
m_materialHueShift = materialHueFromDegrees(instanceValue("materialHueShift", 0).toFloat());
|
||||
|
||||
if (materialHueShift() != MaterialHue()) {
|
||||
auto drawables = iconDrawables();
|
||||
for (auto& d : drawables) {
|
||||
if (d.isImage())
|
||||
d.imagePart().addDirectives(strf("?hueshift=%s", materialHueToDegrees(m_materialHueShift)), false);
|
||||
}
|
||||
setIconDrawables(move(drawables));
|
||||
}
|
||||
|
||||
setTwoHanded(config.getBool("twoHanded", true));
|
||||
|
||||
setCooldownTime(Root::singleton().assets()->json("/items/defaultParameters.config:materialItems.cooldown").toFloat());
|
||||
m_blockRadius = Root::singleton().assets()->json("/items/defaultParameters.config:blockRadius").toFloat();
|
||||
m_altBlockRadius = Root::singleton().assets()->json("/items/defaultParameters.config:altBlockRadius").toFloat();
|
||||
m_multiplace = config.getBool("allowMultiplace", BlockCollisionSet.contains(Root::singleton().materialDatabase()->materialCollisionKind(m_material)));
|
||||
|
||||
m_shifting = false;
|
||||
}
|
||||
|
||||
ItemPtr MaterialItem::clone() const {
|
||||
return make_shared<MaterialItem>(*this);
|
||||
}
|
||||
|
||||
void MaterialItem::init(ToolUserEntity* owner, ToolHand hand) {
|
||||
FireableItem::init(owner, hand);
|
||||
BeamItem::init(owner, hand);
|
||||
}
|
||||
|
||||
void MaterialItem::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
|
||||
FireableItem::update(fireMode, shifting, moves);
|
||||
BeamItem::update(fireMode, shifting, moves);
|
||||
if (shifting || !multiplaceEnabled())
|
||||
setEnd(BeamItem::EndType::Tile);
|
||||
else
|
||||
setEnd(BeamItem::EndType::TileGroup);
|
||||
m_shifting = shifting;
|
||||
}
|
||||
|
||||
List<Drawable> MaterialItem::nonRotatedDrawables() const {
|
||||
return beamDrawables(canPlace(m_shifting));
|
||||
}
|
||||
|
||||
void MaterialItem::fire(FireMode mode, bool shifting, bool edgeTriggered) {
|
||||
if (!initialized() || !ready() || !owner()->inToolRange())
|
||||
return;
|
||||
|
||||
auto layer = (mode == FireMode::Primary || !twoHanded() ? TileLayer::Foreground : TileLayer::Background);
|
||||
TileModificationList modifications;
|
||||
|
||||
float radius;
|
||||
|
||||
if (!shifting)
|
||||
radius = m_blockRadius;
|
||||
else
|
||||
radius = m_altBlockRadius;
|
||||
|
||||
if (!multiplaceEnabled())
|
||||
radius = 1;
|
||||
|
||||
for (auto pos : tileAreaBrush(radius, owner()->aimPosition(), true))
|
||||
modifications.append({pos, PlaceMaterial{layer, materialId(), placementHueShift(pos)}});
|
||||
|
||||
// Make sure not to make any more modifications than we have consumables.
|
||||
if (modifications.size() > count())
|
||||
modifications.resize(count());
|
||||
size_t failed = world()->applyTileModifications(modifications, false).size();
|
||||
if (failed < modifications.size()) {
|
||||
FireableItem::fire(mode, shifting, edgeTriggered);
|
||||
consume(modifications.size() - failed);
|
||||
}
|
||||
}
|
||||
|
||||
MaterialId MaterialItem::materialId() const {
|
||||
return m_material;
|
||||
}
|
||||
|
||||
MaterialHue MaterialItem::materialHueShift() const {
|
||||
return m_materialHueShift;
|
||||
}
|
||||
|
||||
bool MaterialItem::canPlace(bool shifting) const {
|
||||
if (initialized()) {
|
||||
MaterialId material = materialId();
|
||||
|
||||
float radius;
|
||||
if (!shifting)
|
||||
radius = m_blockRadius;
|
||||
else
|
||||
radius = m_altBlockRadius;
|
||||
|
||||
if (!multiplaceEnabled())
|
||||
radius = 1;
|
||||
|
||||
for (auto pos : tileAreaBrush(radius, owner()->aimPosition(), true)) {
|
||||
MaterialHue hueShift = placementHueShift(pos);
|
||||
if (world()->canModifyTile(pos, PlaceMaterial{TileLayer::Foreground, material, hueShift}, false)
|
||||
|| world()->canModifyTile(pos, PlaceMaterial{TileLayer::Background, material, hueShift}, false))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MaterialItem::multiplaceEnabled() const {
|
||||
return m_multiplace && count() > 1;
|
||||
}
|
||||
|
||||
List<PreviewTile> MaterialItem::preview(bool shifting) const {
|
||||
List<PreviewTile> result;
|
||||
if (initialized()) {
|
||||
Color lightColor = Color::rgba(owner()->favoriteColor());
|
||||
Vec3B light = lightColor.toRgb();
|
||||
|
||||
auto material = materialId();
|
||||
auto color = DefaultMaterialColorVariant;
|
||||
|
||||
float radius;
|
||||
|
||||
if (!shifting)
|
||||
radius = m_blockRadius;
|
||||
else
|
||||
radius = m_altBlockRadius;
|
||||
|
||||
if (!multiplaceEnabled())
|
||||
radius = 1;
|
||||
|
||||
size_t c = 0;
|
||||
|
||||
for (auto pos : tileAreaBrush(radius, owner()->aimPosition(), true)) {
|
||||
MaterialHue hueShift = placementHueShift(pos);
|
||||
if (c >= count())
|
||||
break;
|
||||
if (world()->canModifyTile(pos, PlaceMaterial{TileLayer::Foreground, material, hueShift}, false)) {
|
||||
result.append({pos, true, material, hueShift, true});
|
||||
c++;
|
||||
} else if (twoHanded()
|
||||
&& world()->canModifyTile(pos, PlaceMaterial{TileLayer::Background, material, hueShift}, false)) {
|
||||
result.append({pos, true, material, hueShift, true, light, true, color});
|
||||
c++;
|
||||
} else {
|
||||
result.append({pos, true, material, hueShift, true});
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MaterialHue MaterialItem::placementHueShift(Vec2I const& pos) const {
|
||||
if (auto hue = instanceValue("materialHueShift")) {
|
||||
return materialHueFromDegrees(hue.toFloat());
|
||||
} else if (auto worldClient = as<WorldClient>(world())) {
|
||||
auto worldTemplate = worldClient->currentTemplate();
|
||||
return worldTemplate->biomeMaterialHueShift(worldTemplate->blockBiomeIndex(pos[0], pos[1]), m_material);
|
||||
} else {
|
||||
return materialHueShift();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
50
source/game/items/StarMaterialItem.hpp
Normal file
50
source/game/items/StarMaterialItem.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#ifndef STAR_MATERIAL_ITEM_HPP
|
||||
#define STAR_MATERIAL_ITEM_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
#include "StarFireableItem.hpp"
|
||||
#include "StarBeamItem.hpp"
|
||||
#include "StarEntityRendering.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(MaterialItem);
|
||||
|
||||
class MaterialItem : public Item, public FireableItem, public BeamItem {
|
||||
public:
|
||||
MaterialItem(Json const& config, String const& directory, Json const& settings);
|
||||
virtual ~MaterialItem() {}
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
void init(ToolUserEntity* owner, ToolHand hand) override;
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
|
||||
List<Drawable> nonRotatedDrawables() const override;
|
||||
|
||||
void fire(FireMode mode, bool shifting, bool edgeTriggered) override;
|
||||
|
||||
MaterialId materialId() const;
|
||||
MaterialHue materialHueShift() const;
|
||||
|
||||
bool canPlace(bool shifting) const;
|
||||
bool multiplaceEnabled() const;
|
||||
|
||||
// FIXME: Why isn't this a PreviewTileTool then??
|
||||
List<PreviewTile> preview(bool shifting) const;
|
||||
|
||||
private:
|
||||
MaterialHue placementHueShift(Vec2I const& position) const;
|
||||
|
||||
MaterialId m_material;
|
||||
MaterialHue m_materialHueShift;
|
||||
|
||||
float m_blockRadius;
|
||||
float m_altBlockRadius;
|
||||
bool m_shifting;
|
||||
bool m_multiplace;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
107
source/game/items/StarObjectItem.cpp
Normal file
107
source/game/items/StarObjectItem.cpp
Normal file
|
@ -0,0 +1,107 @@
|
|||
#include "StarObjectItem.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarObject.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarObjectDatabase.hpp"
|
||||
#include "StarWorld.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ObjectItem::ObjectItem(Json const& config, String const& directory, Json const& objectParameters)
|
||||
: Item(config, directory, objectParameters), FireableItem(config), BeamItem(config) {
|
||||
setTwoHanded(config.getBool("twoHanded", true));
|
||||
|
||||
// Make sure that all script objects that have retainObjectParametersInItem
|
||||
// start with a blank scriptStorage entry to help them stack properly.
|
||||
if (instanceValue("retainObjectParametersInItem", false).toBool() && instanceValue("scriptStorage").isNull())
|
||||
setInstanceValue("scriptStorage", JsonObject());
|
||||
m_shifting = false;
|
||||
}
|
||||
|
||||
ItemPtr ObjectItem::clone() const {
|
||||
return make_shared<ObjectItem>(*this);
|
||||
}
|
||||
|
||||
void ObjectItem::init(ToolUserEntity* owner, ToolHand hand) {
|
||||
FireableItem::init(owner, hand);
|
||||
BeamItem::init(owner, hand);
|
||||
}
|
||||
|
||||
void ObjectItem::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
|
||||
FireableItem::update(fireMode, shifting, moves);
|
||||
BeamItem::update(fireMode, shifting, moves);
|
||||
setEnd(BeamItem::EndType::Object);
|
||||
m_shifting = shifting;
|
||||
}
|
||||
|
||||
List<Drawable> ObjectItem::nonRotatedDrawables() const {
|
||||
return beamDrawables(canPlace(m_shifting));
|
||||
}
|
||||
|
||||
float ObjectItem::cooldownTime() const {
|
||||
// TODO: Hardcoded
|
||||
return 0.25f;
|
||||
}
|
||||
|
||||
void ObjectItem::fire(FireMode mode, bool shifting, bool edgeTriggered) {
|
||||
if (!ready())
|
||||
return;
|
||||
|
||||
if (placeInWorld(mode, shifting))
|
||||
FireableItem::fire(mode, shifting, edgeTriggered);
|
||||
}
|
||||
|
||||
String ObjectItem::objectName() const {
|
||||
return instanceValue("objectName", "<objectName missing>").toString();
|
||||
}
|
||||
|
||||
Json ObjectItem::objectParameters() const {
|
||||
Json objectParameters = parameters().opt().value(JsonObject{});
|
||||
if (!initialized())
|
||||
return objectParameters;
|
||||
return objectParameters.set("owner", jsonFromMaybe(owner()->uniqueId()));
|
||||
}
|
||||
|
||||
bool ObjectItem::placeInWorld(FireMode, bool shifting) {
|
||||
if (!initialized())
|
||||
throw ItemException("ObjectItem not init'd properly, or user not recognized as Tool User.");
|
||||
|
||||
if (!ready())
|
||||
return false;
|
||||
|
||||
if (!canPlace(shifting))
|
||||
return false;
|
||||
|
||||
auto pos = Vec2I(owner()->aimPosition().floor());
|
||||
auto objectDatabase = Root::singleton().objectDatabase();
|
||||
try {
|
||||
if (auto object = objectDatabase->createForPlacement(world(), objectName(), pos, owner()->walkingDirection(), objectParameters())) {
|
||||
if (consume(1)) {
|
||||
world()->addEntity(object);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (StarException const& e) {
|
||||
Logger::error("Failed to instantiate object for placement. %s %s : %s",
|
||||
objectName(),
|
||||
objectParameters().repr(0, true),
|
||||
outputException(e, true));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ObjectItem::canPlace(bool) const {
|
||||
if (initialized()) {
|
||||
if (owner()->isAdmin() || owner()->inToolRange()) {
|
||||
auto pos = Vec2I(owner()->aimPosition().floor());
|
||||
auto objectDatabase = Root::singleton().objectDatabase();
|
||||
return objectDatabase->canPlaceObject(world(), pos, objectName());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
39
source/game/items/StarObjectItem.hpp
Normal file
39
source/game/items/StarObjectItem.hpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#ifndef STAR_OBJECT_ITEM_HPP
|
||||
#define STAR_OBJECT_ITEM_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
#include "StarFireableItem.hpp"
|
||||
#include "StarBeamItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(ObjectItem);
|
||||
|
||||
class ObjectItem : public Item, public FireableItem, public BeamItem {
|
||||
public:
|
||||
ObjectItem(Json const& config, String const& directory, Json const& objectParameters);
|
||||
virtual ~ObjectItem() {}
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
void init(ToolUserEntity* owner, ToolHand hand) override;
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
|
||||
List<Drawable> nonRotatedDrawables() const override;
|
||||
|
||||
float cooldownTime() const override;
|
||||
void fire(FireMode mode, bool shifting, bool edgeTriggered) override;
|
||||
|
||||
String objectName() const;
|
||||
Json objectParameters() const;
|
||||
|
||||
bool placeInWorld(FireMode mode, bool shifting);
|
||||
bool canPlace(bool shifting) const;
|
||||
|
||||
private:
|
||||
bool m_shifting;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
56
source/game/items/StarThrownItem.cpp
Normal file
56
source/game/items/StarThrownItem.cpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include "StarThrownItem.hpp"
|
||||
#include "StarProjectile.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarProjectileDatabase.hpp"
|
||||
#include "StarWorld.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
ThrownItem::ThrownItem(Json const& config, String const& directory, Json const& itemParameters)
|
||||
: Item(config, directory, itemParameters), SwingableItem(config) {
|
||||
m_projectileType = instanceValue("projectileType").toString();
|
||||
m_projectileConfig = instanceValue("projectileConfig", {});
|
||||
m_ammoUsage = instanceValue("ammoUsage", 1).toUInt();
|
||||
|
||||
auto image = AssetPath::relativeTo(directory, instanceValue("image").toString());
|
||||
m_drawables = {Drawable::makeImage(image, 1.0f / TilePixels, true, Vec2F())};
|
||||
}
|
||||
|
||||
ItemPtr ThrownItem::clone() const {
|
||||
return make_shared<ThrownItem>(*this);
|
||||
}
|
||||
|
||||
List<Drawable> ThrownItem::drawables() const {
|
||||
return m_drawables;
|
||||
}
|
||||
|
||||
List<Drawable> ThrownItem::preview(PlayerPtr const&) const {
|
||||
return iconDrawables();
|
||||
}
|
||||
|
||||
void ThrownItem::fireTriggered() {
|
||||
auto& root = Root::singleton();
|
||||
|
||||
if (initialized()) {
|
||||
Vec2F direction = world()->geometry().diff(owner()->aimPosition(), owner()->position()).normalized();
|
||||
Vec2F firePosition = owner()->position() + ownerFirePosition();
|
||||
if (world()->lineTileCollision(owner()->position(), firePosition))
|
||||
return;
|
||||
|
||||
if (consume(m_ammoUsage)) {
|
||||
auto projectile = root.projectileDatabase()->createProjectile(m_projectileType, m_projectileConfig);
|
||||
projectile->setInitialPosition(firePosition);
|
||||
projectile->setInitialDirection(direction);
|
||||
projectile->setSourceEntity(owner()->entityId(), false);
|
||||
projectile->setPowerMultiplier(owner()->powerMultiplier());
|
||||
world()->addEntity(projectile);
|
||||
}
|
||||
|
||||
FireableItem::fireTriggered();
|
||||
} else {
|
||||
throw ItemException("Thrown item not init'd properly, or user not recognized as Tool User.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
32
source/game/items/StarThrownItem.hpp
Normal file
32
source/game/items/StarThrownItem.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef STAR_THROWN_ITEM_HPP
|
||||
#define STAR_THROWN_ITEM_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
#include "StarDrawable.hpp"
|
||||
#include "StarSwingableItem.hpp"
|
||||
#include "StarPreviewableItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
class ThrownItem : public Item, public SwingableItem, public PreviewableItem {
|
||||
public:
|
||||
ThrownItem(Json const& config, String const& directory, Json const& itemParameters = JsonObject());
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
List<Drawable> preview(PlayerPtr const& viewer = {}) const override;
|
||||
|
||||
protected:
|
||||
void fireTriggered() override;
|
||||
|
||||
private:
|
||||
String m_projectileType;
|
||||
Json m_projectileConfig;
|
||||
size_t m_ammoUsage;
|
||||
List<Drawable> m_drawables;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
709
source/game/items/StarTools.cpp
Normal file
709
source/game/items/StarTools.cpp
Normal file
|
@ -0,0 +1,709 @@
|
|||
#include "StarTools.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarMaterialDatabase.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarWiring.hpp"
|
||||
#include "StarWorld.hpp"
|
||||
#include "StarWorldClient.hpp"
|
||||
#include "StarParticleDatabase.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
MiningTool::MiningTool(Json const& config, String const& directory, Json const& parameters)
|
||||
: Item(config, directory, parameters), SwingableItem(config) {
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
m_image = AssetPath::relativeTo(directory, instanceValue("image").toString());
|
||||
m_frames = instanceValue("frames", 1).toInt();
|
||||
m_frameCycle = instanceValue("animationCycle", 1.0f).toFloat();
|
||||
m_frameTiming = 0;
|
||||
for (size_t i = 0; i < (size_t)m_frames; i++)
|
||||
m_animationFrame.append(m_image.replace("{frame}", strf("%s", i)));
|
||||
m_idleFrame = m_image.replace("{frame}", "idle");
|
||||
m_handPosition = jsonToVec2F(instanceValue("handPosition"));
|
||||
m_blockRadius = instanceValue("blockRadius").toFloat();
|
||||
m_altBlockRadius = instanceValue("altBlockRadius").toFloat();
|
||||
m_strikeSounds = jsonToStringList(instanceValue("strikeSounds"));
|
||||
m_breakSound = instanceValue("breakSound", "").toString();
|
||||
m_pointable = instanceValue("pointable", false).toBool();
|
||||
|
||||
m_toolVolume = assets->json("/sfx.config:miningToolVolume").toFloat();
|
||||
m_blockVolume = assets->json("/sfx.config:miningBlockVolume").toFloat();
|
||||
}
|
||||
|
||||
ItemPtr MiningTool::clone() const {
|
||||
return make_shared<MiningTool>(*this);
|
||||
}
|
||||
|
||||
List<Drawable> MiningTool::drawables() const {
|
||||
if (m_frameTiming == 0) {
|
||||
return {Drawable::makeImage(m_idleFrame, 1.0f / TilePixels, true, -handPosition() / TilePixels)};
|
||||
} else {
|
||||
int frame = std::max(0, std::min(m_frames - 1, (int)std::floor((m_frameTiming / m_frameCycle) * m_frames)));
|
||||
return {Drawable::makeImage(m_animationFrame[frame], 1.0f / TilePixels, true, -handPosition() / TilePixels)};
|
||||
}
|
||||
}
|
||||
|
||||
Vec2F MiningTool::handPosition() const {
|
||||
return m_handPosition;
|
||||
}
|
||||
|
||||
void MiningTool::fire(FireMode mode, bool shifting, bool edgeTriggered) {
|
||||
if (!ready())
|
||||
return;
|
||||
|
||||
auto materialDatabase = Root::singleton().materialDatabase();
|
||||
|
||||
if (initialized()) {
|
||||
bool used = false;
|
||||
int radius = !shifting ? m_blockRadius : m_altBlockRadius;
|
||||
String blockSound;
|
||||
List<Vec2I> brushArea;
|
||||
|
||||
auto layer = (mode == FireMode::Primary ? TileLayer::Foreground : TileLayer::Background);
|
||||
if (owner()->isAdmin() || owner()->inToolRange()) {
|
||||
brushArea = tileAreaBrush(radius, owner()->aimPosition(), true);
|
||||
for (auto pos : brushArea) {
|
||||
blockSound = materialDatabase->miningSound(world()->material(pos, layer), world()->mod(pos, layer));
|
||||
if (!blockSound.empty())
|
||||
break;
|
||||
}
|
||||
if (blockSound.empty()) {
|
||||
for (auto pos : brushArea) {
|
||||
blockSound = materialDatabase->footstepSound(world()->material(pos, layer), world()->mod(pos, layer));
|
||||
if (!blockSound.empty()
|
||||
&& blockSound != Root::singleton().assets()->json("/client.config:defaultFootstepSound").toString())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TileDamage damage;
|
||||
damage.type = TileDamageTypeNames.getLeft(instanceValue("tileDamageType", "blockish").toString());
|
||||
|
||||
if (durabilityStatus() == 0)
|
||||
damage.amount = instanceValue("tileDamageBlunted", 0.1f).toFloat();
|
||||
else
|
||||
damage.amount = instanceValue("tileDamage", 1.0f).toFloat();
|
||||
|
||||
damage.harvestLevel = instanceValue("harvestLevel", 1).toUInt();
|
||||
|
||||
auto damageResult = world()->damageTiles(brushArea, layer, owner()->position(), damage, owner()->entityId());
|
||||
|
||||
if (damageResult != TileDamageResult::None) {
|
||||
used = true;
|
||||
if (!owner()->isAdmin())
|
||||
changeDurability(instanceValue("durabilityPerUse", 1.0f).toFloat());
|
||||
}
|
||||
|
||||
if (damageResult == TileDamageResult::Protected) {
|
||||
blockSound = Root::singleton().assets()->json("/client.config:defaultDingSound").toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (used) {
|
||||
owner()->addSound(Random::randValueFrom(m_strikeSounds), m_toolVolume);
|
||||
owner()->addSound(blockSound, m_blockVolume);
|
||||
List<Particle> miningParticles;
|
||||
for (auto pos : brushArea) {
|
||||
if (auto miningParticleConfig = materialDatabase->miningParticle(world()->material(pos, layer), world()->mod(pos, layer))) {
|
||||
auto miningParticle = miningParticleConfig->instance();
|
||||
miningParticle.position += (Vec2F)pos;
|
||||
miningParticles.append(miningParticle);
|
||||
}
|
||||
}
|
||||
owner()->addParticles(miningParticles);
|
||||
SwingableItem::fire(mode, shifting, edgeTriggered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MiningTool::update(FireMode mode, bool shifting, HashSet<MoveControlType> const& moves) {
|
||||
SwingableItem::update(mode, shifting, moves);
|
||||
|
||||
if (!ready() && !coolingDown())
|
||||
m_frameTiming = std::fmod((m_frameTiming + WorldTimestep), m_frameCycle);
|
||||
else
|
||||
m_frameTiming = 0;
|
||||
}
|
||||
|
||||
float MiningTool::durabilityStatus() {
|
||||
return clamp(
|
||||
1.0f - instanceValue("durabilityHit", 0.0f).toFloat() / instanceValue("durability").toFloat(), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
float MiningTool::getAngle(float aimAngle) {
|
||||
if ((!ready() && !coolingDown()) || !m_pointable)
|
||||
return SwingableItem::getAngle(aimAngle);
|
||||
return aimAngle;
|
||||
}
|
||||
|
||||
void MiningTool::changeDurability(float amount) {
|
||||
setInstanceValue("durabilityHit", clamp(instanceValue("durabilityHit", 0.0f).toFloat() + amount, 0.0f, instanceValue("durability").toFloat()));
|
||||
if (durabilityStatus() == 0.0f && !instanceValue("canBeRepaired", false).toBool()) {
|
||||
owner()->addSound(m_breakSound);
|
||||
consume(1);
|
||||
}
|
||||
}
|
||||
|
||||
HarvestingTool::HarvestingTool(Json const& config, String const& directory, Json const& parameters)
|
||||
: Item(config, directory, parameters), SwingableItem(config) {
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
m_image = AssetPath::relativeTo(directory, instanceValue("image").toString());
|
||||
m_frames = instanceValue("frames", 1).toInt();
|
||||
m_frameCycle = instanceValue("animationCycle", 1.0f).toFloat();
|
||||
for (size_t i = 0; i < (size_t)m_frames; i++)
|
||||
m_animationFrame.append(m_image.replace("{frame}", strf("%s", i)));
|
||||
m_idleFrame = m_image.replace("{frame}", "idle");
|
||||
|
||||
m_handPosition = jsonToVec2F(instanceValue("handPosition"));
|
||||
m_strikeSounds = jsonToStringList(instanceValue("strikeSounds"));
|
||||
m_toolVolume = assets->json("/sfx.config:harvestToolVolume").toFloat();
|
||||
m_harvestPower = instanceValue("harvestPower", 1.0f).toFloat();
|
||||
m_frameTiming = 0;
|
||||
}
|
||||
|
||||
ItemPtr HarvestingTool::clone() const {
|
||||
return make_shared<HarvestingTool>(*this);
|
||||
}
|
||||
|
||||
List<Drawable> HarvestingTool::drawables() const {
|
||||
if (m_frameTiming == 0)
|
||||
return {Drawable::makeImage(m_idleFrame, 1.0f / TilePixels, true, -handPosition() / TilePixels)};
|
||||
else {
|
||||
int frame = std::max(0, std::min(m_frames - 1, (int)std::floor((m_frameTiming / m_frameCycle) * m_frames)));
|
||||
return {Drawable::makeImage(m_animationFrame[frame], 1.0f / TilePixels, true, -handPosition() / TilePixels)};
|
||||
}
|
||||
}
|
||||
|
||||
Vec2F HarvestingTool::handPosition() const {
|
||||
return m_handPosition;
|
||||
}
|
||||
|
||||
void HarvestingTool::fire(FireMode mode, bool shifting, bool edgeTriggered) {
|
||||
if (!ready())
|
||||
return;
|
||||
|
||||
if (owner()) {
|
||||
bool used = false;
|
||||
|
||||
if (owner()->isAdmin() || owner()->inToolRange()) {
|
||||
auto layer = (mode == FireMode::Primary ? TileLayer::Foreground : TileLayer::Background);
|
||||
used = world()->damageTile(Vec2I::floor(owner()->aimPosition()), layer, owner()->position(), {TileDamageType::Plantish, m_harvestPower}) != TileDamageResult::None;
|
||||
}
|
||||
|
||||
if (used) {
|
||||
owner()->addSound(Random::randValueFrom(m_strikeSounds), m_toolVolume);
|
||||
SwingableItem::fire(mode, shifting, edgeTriggered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HarvestingTool::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
|
||||
SwingableItem::update(fireMode, shifting, moves);
|
||||
|
||||
if (!ready() && !coolingDown())
|
||||
m_frameTiming = std::fmod((m_frameTiming + WorldTimestep), m_frameCycle);
|
||||
else
|
||||
m_frameTiming = 0;
|
||||
}
|
||||
|
||||
float HarvestingTool::getAngle(float aimAngle) {
|
||||
if (!ready() && !coolingDown())
|
||||
return SwingableItem::getAngle(aimAngle);
|
||||
return aimAngle;
|
||||
}
|
||||
|
||||
Flashlight::Flashlight(Json const& config, String const& directory, Json const& parameters)
|
||||
: Item(config, directory, parameters) {
|
||||
m_image = AssetPath::relativeTo(directory, instanceValue("image").toString());
|
||||
m_handPosition = jsonToVec2F(instanceValue("handPosition"));
|
||||
m_lightPosition = jsonToVec2F(instanceValue("lightPosition"));
|
||||
m_lightColor = jsonToColor(instanceValue("lightColor"));
|
||||
m_beamWidth = instanceValue("beamLevel").toFloat();
|
||||
m_ambientFactor = instanceValue("beamAmbience").toFloat();
|
||||
}
|
||||
|
||||
ItemPtr Flashlight::clone() const {
|
||||
return make_shared<Flashlight>(*this);
|
||||
}
|
||||
|
||||
List<Drawable> Flashlight::drawables() const {
|
||||
return {Drawable::makeImage(m_image, 1.0f / TilePixels, true, -m_handPosition / TilePixels)};
|
||||
}
|
||||
|
||||
List<LightSource> Flashlight::lightSources() const {
|
||||
if (!initialized())
|
||||
return {};
|
||||
|
||||
float angle = world()->geometry().diff(owner()->aimPosition(), owner()->position()).angle();
|
||||
LightSource lightSource;
|
||||
lightSource.pointLight = true;
|
||||
lightSource.position = owner()->position() + owner()->handPosition(hand(), (m_lightPosition - m_handPosition) / TilePixels);
|
||||
lightSource.color = m_lightColor.toRgb();
|
||||
lightSource.pointBeam = m_beamWidth;
|
||||
lightSource.beamAngle = angle;
|
||||
lightSource.beamAmbience = m_ambientFactor;
|
||||
return {move(lightSource)};
|
||||
}
|
||||
|
||||
WireTool::WireTool(Json const& config, String const& directory, Json const& parameters)
|
||||
: Item(config, directory, parameters), FireableItem(config), BeamItem(config.setAll(parameters.toObject())) {
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
m_handPosition = jsonToVec2F(instanceValue("handPosition"));
|
||||
m_strikeSounds = jsonToStringList(instanceValue("strikeSounds"));
|
||||
m_toolVolume = assets->json("/sfx.config:miningToolVolume").toFloat();
|
||||
m_wireConnector = 0;
|
||||
m_endType = EndType::Wire;
|
||||
}
|
||||
|
||||
ItemPtr WireTool::clone() const {
|
||||
return make_shared<WireTool>(*this);
|
||||
}
|
||||
|
||||
void WireTool::init(ToolUserEntity* owner, ToolHand hand) {
|
||||
FireableItem::init(owner, hand);
|
||||
BeamItem::init(owner, hand);
|
||||
m_wireConnector = 0;
|
||||
}
|
||||
|
||||
void WireTool::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
|
||||
FireableItem::update(fireMode, shifting, moves);
|
||||
BeamItem::update(fireMode, shifting, moves);
|
||||
}
|
||||
|
||||
List<Drawable> WireTool::drawables() const {
|
||||
return BeamItem::drawables();
|
||||
}
|
||||
|
||||
List<Drawable> WireTool::nonRotatedDrawables() const {
|
||||
if (m_wireConnector && m_wireConnector->connecting())
|
||||
return BeamItem::nonRotatedDrawables();
|
||||
return {};
|
||||
}
|
||||
|
||||
void WireTool::setEnd(EndType) {
|
||||
m_endType = EndType::Wire;
|
||||
}
|
||||
|
||||
Vec2F WireTool::handPosition() const {
|
||||
return m_handPosition;
|
||||
}
|
||||
|
||||
void WireTool::fire(FireMode mode, bool shifting, bool edgeTriggered) {
|
||||
if (!ready())
|
||||
return;
|
||||
|
||||
auto ownerp = owner();
|
||||
auto worldp = world();
|
||||
|
||||
if (ownerp && worldp && m_wireConnector) {
|
||||
Vec2F pos(ownerp->aimPosition());
|
||||
if (ownerp->isAdmin() || ownerp->inToolRange()) {
|
||||
auto swingResult = m_wireConnector->swing(worldp->geometry(), pos, mode);
|
||||
if (swingResult == WireConnector::Connect) {
|
||||
ownerp->addSound(Random::randValueFrom(m_strikeSounds), m_toolVolume);
|
||||
FireableItem::fire(mode, shifting, edgeTriggered);
|
||||
} else if (swingResult == WireConnector::Mismatch || swingResult == WireConnector::Protected) {
|
||||
auto wireErrorSound = Root::singleton().assets()->json("/client.config:wireFailSound").toString();
|
||||
ownerp->addSound(wireErrorSound, m_toolVolume);
|
||||
FireableItem::fire(mode, shifting, edgeTriggered);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float WireTool::getAngle(float aimAngle) {
|
||||
return BeamItem::getAngle(aimAngle);
|
||||
}
|
||||
|
||||
void WireTool::setConnector(WireConnector* connector) {
|
||||
m_wireConnector = connector;
|
||||
}
|
||||
|
||||
BeamMiningTool::BeamMiningTool(Json const& config, String const& directory, Json const& parameters)
|
||||
: Item(config, directory, parameters), FireableItem(config), BeamItem(config.setAll(parameters.toObject())) {
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
m_blockRadius = instanceValue("blockRadius").toFloat();
|
||||
m_altBlockRadius = instanceValue("altBlockRadius").toFloat();
|
||||
m_tileDamage = instanceValue("tileDamage", 1.0f).toFloat();
|
||||
m_harvestLevel = instanceValue("harvestLevel", 1).toUInt();
|
||||
m_canCollectLiquid = instanceValue("canCollectLiquid", false).toBool();
|
||||
m_strikeSounds = jsonToStringList(instanceValue("strikeSounds"));
|
||||
m_toolVolume = assets->json("/sfx.config:miningToolVolume").toFloat();
|
||||
m_blockVolume = assets->json("/sfx.config:miningBlockVolume").toFloat();
|
||||
m_endType = EndType::Object;
|
||||
|
||||
m_inhandStatusEffects = instanceValue("inhandStatusEffects", JsonArray()).toArray().transformed(jsonToPersistentStatusEffect);
|
||||
}
|
||||
|
||||
ItemPtr BeamMiningTool::clone() const {
|
||||
return make_shared<BeamMiningTool>(*this);
|
||||
}
|
||||
|
||||
List<Drawable> BeamMiningTool::drawables() const {
|
||||
return BeamItem::drawables();
|
||||
}
|
||||
|
||||
void BeamMiningTool::setEnd(EndType) {
|
||||
m_endType = EndType::Object;
|
||||
}
|
||||
|
||||
List<PreviewTile> BeamMiningTool::preview(bool shifting) const {
|
||||
List<PreviewTile> result;
|
||||
auto ownerp = owner();
|
||||
auto worldp = world();
|
||||
|
||||
if (ownerp && worldp) {
|
||||
if (ownerp->isAdmin() || ownerp->inToolRange()) {
|
||||
Vec3B light = Color::rgba(ownerp->favoriteColor()).toRgb();
|
||||
int radius = !shifting ? m_blockRadius : m_altBlockRadius;
|
||||
for (auto pos : tileAreaBrush(radius, ownerp->aimPosition(), true)) {
|
||||
if (worldp->tileIsOccupied(pos, TileLayer::Foreground, true)) {
|
||||
result.append({pos, true, light, true});
|
||||
} else if (worldp->tileIsOccupied(pos, TileLayer::Background, true)) {
|
||||
result.append({pos, false, light, true});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void BeamMiningTool::init(ToolUserEntity* owner, ToolHand hand) {
|
||||
FireableItem::init(owner, hand);
|
||||
BeamItem::init(owner, hand);
|
||||
}
|
||||
|
||||
void BeamMiningTool::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
|
||||
FireableItem::update(fireMode, shifting, moves);
|
||||
BeamItem::update(fireMode, shifting, moves);
|
||||
}
|
||||
|
||||
List<PersistentStatusEffect> BeamMiningTool::statusEffects() const {
|
||||
return m_inhandStatusEffects;
|
||||
}
|
||||
|
||||
List<Drawable> BeamMiningTool::nonRotatedDrawables() const {
|
||||
if (!ready() && !coolingDown())
|
||||
return BeamItem::nonRotatedDrawables();
|
||||
return {};
|
||||
}
|
||||
|
||||
void BeamMiningTool::fire(FireMode mode, bool shifting, bool edgeTriggered) {
|
||||
if (!ready())
|
||||
return;
|
||||
|
||||
auto materialDatabase = Root::singleton().materialDatabase();
|
||||
|
||||
auto worldp = world();
|
||||
auto ownerp = owner();
|
||||
if (ownerp && worldp) {
|
||||
bool used = false;
|
||||
int radius = !shifting ? m_blockRadius : m_altBlockRadius;
|
||||
String blockSound;
|
||||
List<Vec2I> brushArea;
|
||||
|
||||
auto layer = (mode == FireMode::Primary ? TileLayer::Foreground : TileLayer::Background);
|
||||
if (ownerp->isAdmin() || ownerp->inToolRange()) {
|
||||
brushArea = tileAreaBrush(radius, ownerp->aimPosition(), true);
|
||||
auto aimPosition = Vec2I(ownerp->aimPosition());
|
||||
|
||||
for (auto pos : brushArea) {
|
||||
blockSound = materialDatabase->miningSound(worldp->material(pos, layer), worldp->mod(pos, layer));
|
||||
if (!blockSound.empty())
|
||||
break;
|
||||
}
|
||||
if (blockSound.empty()) {
|
||||
for (auto pos : brushArea) {
|
||||
blockSound = materialDatabase->footstepSound(worldp->material(pos, layer), worldp->mod(pos, layer));
|
||||
if (!blockSound.empty()
|
||||
&& blockSound != Root::singleton().assets()->json("/client.config:defaultFootstepSound").toString())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto damageResult = worldp->damageTiles(List<Vec2I>{brushArea}, layer, ownerp->position(), {TileDamageType::Beamish, m_tileDamage, m_harvestLevel}, ownerp->entityId());
|
||||
used = damageResult != TileDamageResult::None;
|
||||
|
||||
if (damageResult == TileDamageResult::Protected) {
|
||||
blockSound = Root::singleton().assets()->json("/client.config:defaultDingSound").toString();
|
||||
}
|
||||
|
||||
if (!used && m_canCollectLiquid && layer == TileLayer::Foreground && worldp->material(aimPosition, TileLayer::Foreground) == EmptyMaterialId) {
|
||||
auto targetLiquid = worldp->liquidLevel(aimPosition).liquid;
|
||||
List<Vec2I> drainTiles;
|
||||
float totalLiquid = 0;
|
||||
for (auto pos : brushArea) {
|
||||
if (worldp->isTileProtected(pos))
|
||||
continue;
|
||||
|
||||
auto liquid = worldp->liquidLevel(pos);
|
||||
if (liquid.liquid != EmptyLiquidId) {
|
||||
if (targetLiquid == EmptyLiquidId)
|
||||
targetLiquid = liquid.liquid;
|
||||
|
||||
if (liquid.liquid == targetLiquid) {
|
||||
totalLiquid += liquid.level;
|
||||
drainTiles.append(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float bucketSize = Root::singleton().assets()->json("/items/defaultParameters.config:liquidItems.bucketSize").toUInt();
|
||||
if (totalLiquid >= bucketSize) {
|
||||
if (auto clientWorld = as<WorldClient>(worldp))
|
||||
clientWorld->collectLiquid(drainTiles, targetLiquid);
|
||||
|
||||
blockSound = Root::singleton().assets()->json("/items/defaultParameters.config:liquidBlockSound").toString();
|
||||
|
||||
used = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (used) {
|
||||
ownerp->addSound(Random::randValueFrom(m_strikeSounds), m_toolVolume);
|
||||
ownerp->addSound(blockSound, m_blockVolume);
|
||||
List<Particle> miningParticles;
|
||||
for (auto pos : brushArea) {
|
||||
if (auto miningParticleConfig = materialDatabase->miningParticle(worldp->material(pos, layer), worldp->mod(pos, layer))) {
|
||||
auto miningParticle = miningParticleConfig->instance();
|
||||
miningParticle.position += (Vec2F)pos;
|
||||
miningParticles.append(miningParticle);
|
||||
}
|
||||
}
|
||||
ownerp->addParticles(miningParticles);
|
||||
FireableItem::fire(mode, shifting, edgeTriggered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float BeamMiningTool::getAngle(float angle) {
|
||||
return BeamItem::getAngle(angle);
|
||||
}
|
||||
|
||||
TillingTool::TillingTool(Json const& config, String const& directory, Json const& parameters)
|
||||
: Item(config, directory, parameters), SwingableItem(config) {
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
m_image = AssetPath::relativeTo(directory, instanceValue("image").toString());
|
||||
m_frames = instanceValue("frames", 1).toInt();
|
||||
m_frameCycle = instanceValue("animationCycle", 1.0f).toFloat();
|
||||
for (size_t i = 0; i < (size_t)m_frames; i++)
|
||||
m_animationFrame.append(m_image.replace("{frame}", strf("%s", i)));
|
||||
m_idleFrame = m_image.replace("{frame}", "idle");
|
||||
|
||||
m_handPosition = jsonToVec2F(instanceValue("handPosition"));
|
||||
m_strikeSounds = jsonToStringList(instanceValue("strikeSounds"));
|
||||
m_toolVolume = assets->json("/sfx.config:harvestToolVolume").toFloat();
|
||||
m_frameTiming = 0;
|
||||
}
|
||||
|
||||
ItemPtr TillingTool::clone() const {
|
||||
return make_shared<TillingTool>(*this);
|
||||
}
|
||||
|
||||
List<Drawable> TillingTool::drawables() const {
|
||||
if (m_frameTiming == 0)
|
||||
return {Drawable::makeImage(m_idleFrame, 1.0f / TilePixels, true, -handPosition() / TilePixels)};
|
||||
else {
|
||||
int frame = std::max(0, std::min(m_frames - 1, (int)std::floor((m_frameTiming / m_frameCycle) * m_frames)));
|
||||
return {Drawable::makeImage(m_animationFrame[frame], 1.0f / TilePixels, true, -handPosition() / TilePixels)};
|
||||
}
|
||||
}
|
||||
|
||||
Vec2F TillingTool::handPosition() const {
|
||||
return m_handPosition;
|
||||
}
|
||||
|
||||
void TillingTool::fire(FireMode mode, bool shifting, bool edgeTriggered) {
|
||||
if (!ready())
|
||||
return;
|
||||
|
||||
auto strikeSound = Random::randValueFrom(m_strikeSounds);
|
||||
|
||||
if (owner() && world()) {
|
||||
auto materialDatabase = Root::singleton().materialDatabase();
|
||||
Vec2I pos(owner()->aimPosition().floor());
|
||||
|
||||
if (world()->material(pos + Vec2I(0, 1), TileLayer::Foreground) != EmptyMaterialId)
|
||||
return;
|
||||
|
||||
bool used = false;
|
||||
for (auto layer : {TileLayer::Foreground, TileLayer::Background}) {
|
||||
if (world()->material(pos, layer) == EmptyMaterialId)
|
||||
pos = pos - Vec2I(0, 1);
|
||||
|
||||
if ((layer == TileLayer::Background)
|
||||
&& world()->material(pos + Vec2I(0, 1), TileLayer::Background) != EmptyMaterialId)
|
||||
continue;
|
||||
|
||||
if (owner()->isAdmin() || owner()->inToolRange()) {
|
||||
auto currentMod = world()->mod(pos, layer);
|
||||
auto material = world()->material(pos, layer);
|
||||
auto tilledMod = materialDatabase->tilledModFor(material);
|
||||
|
||||
if (tilledMod != NoModId && currentMod == NoModId) {
|
||||
if (world()->modifyTile(pos, PlaceMod{layer, tilledMod, MaterialHue()}, true))
|
||||
used = true;
|
||||
} else if (currentMod != tilledMod) {
|
||||
auto damageResult = world()->damageTile(pos, layer, owner()->position(), {TileDamageType::Tilling, 1.0f});
|
||||
used = damageResult != TileDamageResult::None;
|
||||
if (damageResult == TileDamageResult::Protected) {
|
||||
strikeSound = Root::singleton().assets()->json("/client.config:defaultDingSound").toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (used) {
|
||||
owner()->addSound(strikeSound, m_toolVolume);
|
||||
SwingableItem::fire(mode, shifting, edgeTriggered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TillingTool::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
|
||||
SwingableItem::update(fireMode, shifting, moves);
|
||||
|
||||
if (!ready() && !coolingDown())
|
||||
m_frameTiming = std::fmod((m_frameTiming + WorldTimestep), m_frameCycle);
|
||||
else
|
||||
m_frameTiming = 0;
|
||||
}
|
||||
|
||||
float TillingTool::getAngle(float aimAngle) {
|
||||
if (!ready() && !coolingDown())
|
||||
return SwingableItem::getAngle(aimAngle);
|
||||
return aimAngle;
|
||||
}
|
||||
|
||||
PaintingBeamTool::PaintingBeamTool(Json const& config, String const& directory, Json const& parameters)
|
||||
: Item(config, directory, parameters), FireableItem(config), BeamItem(config) {
|
||||
auto assets = Root::singleton().assets();
|
||||
|
||||
m_blockRadius = instanceValue("blockRadius").toFloat();
|
||||
m_altBlockRadius = instanceValue("altBlockRadius").toFloat();
|
||||
m_strikeSounds = jsonToStringList(instanceValue("strikeSounds"));
|
||||
m_toolVolume = assets->json("/sfx.config:miningToolVolume").toFloat();
|
||||
m_blockVolume = assets->json("/sfx.config:miningBlockVolume").toFloat();
|
||||
m_endType = EndType::Object;
|
||||
|
||||
for (auto color : instanceValue("colorNumbers").toArray())
|
||||
m_colors.append(jsonToColor(color));
|
||||
|
||||
m_colorKeys = jsonToStringList(instanceValue("colorKeys"));
|
||||
|
||||
m_colorIndex = instanceValue("colorIndex", 0).toInt();
|
||||
m_color = m_colors[m_colorIndex].toRgba();
|
||||
}
|
||||
|
||||
ItemPtr PaintingBeamTool::clone() const {
|
||||
return make_shared<PaintingBeamTool>(*this);
|
||||
}
|
||||
|
||||
List<Drawable> PaintingBeamTool::drawables() const {
|
||||
auto result = BeamItem::drawables();
|
||||
for (auto& entry : result) {
|
||||
if (entry.isImage())
|
||||
entry.imagePart().image = entry.imagePart().image + m_colorKeys[m_colorIndex];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void PaintingBeamTool::setEnd(EndType type) {
|
||||
_unused(type);
|
||||
m_endType = EndType::Object;
|
||||
}
|
||||
|
||||
void PaintingBeamTool::update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) {
|
||||
BeamItem::update(fireMode, shifting, moves);
|
||||
FireableItem::update(fireMode, shifting, moves);
|
||||
}
|
||||
|
||||
List<PreviewTile> PaintingBeamTool::preview(bool shifting) const {
|
||||
List<PreviewTile> result;
|
||||
auto ownerp = owner();
|
||||
auto worldp = world();
|
||||
if (ownerp && worldp) {
|
||||
Vec3B light = Color::White.toRgb();
|
||||
|
||||
if (ownerp->isAdmin() || ownerp->inToolRange()) {
|
||||
int radius = !shifting ? m_blockRadius : m_altBlockRadius;
|
||||
|
||||
for (auto pos : tileAreaBrush(radius, ownerp->aimPosition(), true)) {
|
||||
if (worldp->canModifyTile(pos, PlaceMaterialColor{TileLayer::Foreground, (MaterialColorVariant)m_colorIndex}, true)) {
|
||||
result.append({pos, true, NullMaterialId, MaterialHue(), false, light, true, (MaterialColorVariant)m_colorIndex});
|
||||
} else if (worldp->canModifyTile(pos, PlaceMaterialColor{TileLayer::Background, (MaterialColorVariant)m_colorIndex}, true)) {
|
||||
result.append({pos, false, NullMaterialId, MaterialHue(), false, light, true, (MaterialColorVariant)m_colorIndex});
|
||||
} else if (worldp->canModifyTile(pos, PlaceMaterialColor{TileLayer::Foreground, DefaultMaterialColorVariant}, true)) {
|
||||
result.append({pos, true, NullMaterialId, MaterialHue(), false, light, true, DefaultMaterialColorVariant});
|
||||
} else if (worldp->canModifyTile(pos, PlaceMaterialColor{TileLayer::Background, DefaultMaterialColorVariant}, true)) {
|
||||
result.append({pos, false, NullMaterialId, MaterialHue(), false, light, true, DefaultMaterialColorVariant});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void PaintingBeamTool::init(ToolUserEntity* owner, ToolHand hand) {
|
||||
FireableItem::init(owner, hand);
|
||||
BeamItem::init(owner, hand);
|
||||
m_color = m_colors[m_colorIndex].toRgba();
|
||||
}
|
||||
|
||||
List<Drawable> PaintingBeamTool::nonRotatedDrawables() const {
|
||||
if (!coolingDown())
|
||||
return BeamItem::nonRotatedDrawables();
|
||||
return {};
|
||||
}
|
||||
|
||||
void PaintingBeamTool::fire(FireMode mode, bool shifting, bool edgeTriggered) {
|
||||
if (!ready())
|
||||
return;
|
||||
|
||||
if (mode == FireMode::Alt && edgeTriggered) {
|
||||
m_colorIndex = (m_colorIndex + 1) % m_colors.size();
|
||||
m_color = m_colors[m_colorIndex].toRgba();
|
||||
setInstanceValue("colorIndex", m_colorIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode == FireMode::Primary) {
|
||||
auto worldp = world();
|
||||
auto ownerp = owner();
|
||||
if (ownerp && worldp) {
|
||||
bool used = false;
|
||||
int radius = !shifting ? m_blockRadius : m_altBlockRadius;
|
||||
|
||||
if (ownerp->isAdmin() || ownerp->inToolRange()) {
|
||||
for (auto pos : tileAreaBrush(radius, ownerp->aimPosition(), true)) {
|
||||
TileModificationList modifications = {
|
||||
{pos, PlaceMaterialColor{TileLayer::Foreground, (MaterialColorVariant)m_colorIndex}},
|
||||
{pos, PlaceMaterialColor{TileLayer::Background, (MaterialColorVariant)m_colorIndex}}
|
||||
};
|
||||
auto failed = worldp->applyTileModifications(modifications, true);
|
||||
if (failed.count() < 2)
|
||||
used = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (used) {
|
||||
ownerp->addSound(Random::randValueFrom(m_strikeSounds), m_toolVolume);
|
||||
FireableItem::fire(mode, shifting, edgeTriggered);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float PaintingBeamTool::getAngle(float angle) {
|
||||
return BeamItem::getAngle(angle);
|
||||
}
|
||||
|
||||
}
|
244
source/game/items/StarTools.hpp
Normal file
244
source/game/items/StarTools.hpp
Normal file
|
@ -0,0 +1,244 @@
|
|||
#ifndef STAR_TOOLS_HPP
|
||||
#define STAR_TOOLS_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
#include "StarBeamItem.hpp"
|
||||
#include "StarSwingableItem.hpp"
|
||||
#include "StarDurabilityItem.hpp"
|
||||
#include "StarPointableItem.hpp"
|
||||
#include "StarFireableItem.hpp"
|
||||
#include "StarEntityRendering.hpp"
|
||||
#include "StarPreviewTileTool.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(World);
|
||||
STAR_CLASS(WireConnector);
|
||||
STAR_CLASS(ToolUserEntity);
|
||||
|
||||
STAR_CLASS(MiningTool);
|
||||
STAR_CLASS(HarvestingTool);
|
||||
STAR_CLASS(WireTool);
|
||||
STAR_CLASS(Flashlight);
|
||||
STAR_CLASS(BeamMiningTool);
|
||||
STAR_CLASS(TillingTool);
|
||||
STAR_CLASS(PaintingBeamTool);
|
||||
|
||||
class MiningTool : public Item, public SwingableItem, public DurabilityItem {
|
||||
public:
|
||||
MiningTool(Json const& config, String const& directory, Json const& parameters = JsonObject());
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
// In pixels, offset from image center
|
||||
Vec2F handPosition() const override;
|
||||
void fire(FireMode mode, bool shifting, bool edgeTriggered) override;
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
|
||||
float durabilityStatus() override;
|
||||
|
||||
float getAngle(float aimAngle) override;
|
||||
|
||||
private:
|
||||
void changeDurability(float amount);
|
||||
|
||||
String m_image;
|
||||
int m_frames;
|
||||
float m_frameCycle;
|
||||
float m_frameTiming;
|
||||
List<String> m_animationFrame;
|
||||
String m_idleFrame;
|
||||
|
||||
Vec2F m_handPosition;
|
||||
float m_blockRadius;
|
||||
float m_altBlockRadius;
|
||||
|
||||
StringList m_strikeSounds;
|
||||
String m_breakSound;
|
||||
float m_toolVolume;
|
||||
float m_blockVolume;
|
||||
|
||||
bool m_pointable;
|
||||
};
|
||||
|
||||
class HarvestingTool : public Item, public SwingableItem {
|
||||
public:
|
||||
HarvestingTool(Json const& config, String const& directory, Json const& parameters = JsonObject());
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
// In pixels, offset from image center
|
||||
Vec2F handPosition() const override;
|
||||
void fire(FireMode mode, bool shifting, bool edgeTriggered) override;
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
float getAngle(float aimAngle) override;
|
||||
|
||||
private:
|
||||
String m_image;
|
||||
int m_frames;
|
||||
float m_frameCycle;
|
||||
float m_frameTiming;
|
||||
List<String> m_animationFrame;
|
||||
String m_idleFrame;
|
||||
|
||||
Vec2F m_handPosition;
|
||||
|
||||
String m_idleSound;
|
||||
StringList m_strikeSounds;
|
||||
float m_toolVolume;
|
||||
float m_harvestPower;
|
||||
};
|
||||
|
||||
class Flashlight : public Item, public PointableItem, public ToolUserItem {
|
||||
public:
|
||||
Flashlight(Json const& config, String const& directory, Json const& parameters = JsonObject());
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
|
||||
List<LightSource> lightSources() const;
|
||||
|
||||
private:
|
||||
String m_image;
|
||||
Vec2F m_handPosition;
|
||||
Vec2F m_lightPosition;
|
||||
Color m_lightColor;
|
||||
float m_beamWidth;
|
||||
float m_ambientFactor;
|
||||
};
|
||||
|
||||
class WireTool : public Item, public FireableItem, public PointableItem, public BeamItem {
|
||||
public:
|
||||
WireTool(Json const& config, String const& directory, Json const& parameters = JsonObject());
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
void init(ToolUserEntity* owner, ToolHand hand) override;
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
List<Drawable> nonRotatedDrawables() const override;
|
||||
|
||||
void setEnd(EndType type) override;
|
||||
|
||||
// In pixels, offset from image center
|
||||
Vec2F handPosition() const override;
|
||||
void fire(FireMode mode, bool shifting, bool edgeTriggered) override;
|
||||
float getAngle(float aimAngle) override;
|
||||
|
||||
void setConnector(WireConnector* connector);
|
||||
|
||||
private:
|
||||
String m_image;
|
||||
Vec2F m_handPosition;
|
||||
|
||||
StringList m_strikeSounds;
|
||||
float m_toolVolume;
|
||||
|
||||
WireConnector* m_wireConnector;
|
||||
};
|
||||
|
||||
class BeamMiningTool : public Item, public FireableItem, public PreviewTileTool, public PointableItem, public BeamItem {
|
||||
public:
|
||||
BeamMiningTool(Json const& config, String const& directory, Json const& parameters = JsonObject());
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
|
||||
virtual void setEnd(EndType type) override;
|
||||
virtual List<PreviewTile> preview(bool shifting) const override;
|
||||
virtual List<Drawable> nonRotatedDrawables() const override;
|
||||
virtual void fire(FireMode mode, bool shifting, bool edgeTriggered) override;
|
||||
|
||||
float getAngle(float angle) override;
|
||||
|
||||
void init(ToolUserEntity* owner, ToolHand hand) override;
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
|
||||
List<PersistentStatusEffect> statusEffects() const override;
|
||||
|
||||
private:
|
||||
float m_blockRadius;
|
||||
float m_altBlockRadius;
|
||||
|
||||
float m_tileDamage;
|
||||
unsigned m_harvestLevel;
|
||||
bool m_canCollectLiquid;
|
||||
|
||||
StringList m_strikeSounds;
|
||||
float m_toolVolume;
|
||||
float m_blockVolume;
|
||||
|
||||
List<PersistentStatusEffect> m_inhandStatusEffects;
|
||||
};
|
||||
|
||||
class TillingTool : public Item, public SwingableItem {
|
||||
public:
|
||||
TillingTool(Json const& config, String const& directory, Json const& parameters = JsonObject());
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
// In pixels, offset from image center
|
||||
Vec2F handPosition() const override;
|
||||
void fire(FireMode mode, bool shifting, bool edgeTriggered) override;
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
float getAngle(float aimAngle) override;
|
||||
|
||||
private:
|
||||
String m_image;
|
||||
int m_frames;
|
||||
float m_frameCycle;
|
||||
float m_frameTiming;
|
||||
List<String> m_animationFrame;
|
||||
String m_idleFrame;
|
||||
|
||||
Vec2F m_handPosition;
|
||||
|
||||
String m_idleSound;
|
||||
StringList m_strikeSounds;
|
||||
float m_toolVolume;
|
||||
};
|
||||
|
||||
class PaintingBeamTool
|
||||
: public Item,
|
||||
public FireableItem,
|
||||
public PreviewTileTool,
|
||||
public PointableItem,
|
||||
public BeamItem {
|
||||
public:
|
||||
PaintingBeamTool(Json const& config, String const& directory, Json const& parameters = JsonObject());
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
|
||||
void setEnd(EndType type) override;
|
||||
void update(FireMode fireMode, bool shifting, HashSet<MoveControlType> const& moves) override;
|
||||
List<PreviewTile> preview(bool shifting) const override;
|
||||
void init(ToolUserEntity* owner, ToolHand hand) override;
|
||||
List<Drawable> nonRotatedDrawables() const override;
|
||||
void fire(FireMode mode, bool shifting, bool edgeTriggered) override;
|
||||
|
||||
float getAngle(float angle) override;
|
||||
|
||||
private:
|
||||
List<Color> m_colors;
|
||||
List<String> m_colorKeys;
|
||||
int m_colorIndex;
|
||||
|
||||
float m_blockRadius;
|
||||
float m_altBlockRadius;
|
||||
|
||||
StringList m_strikeSounds;
|
||||
float m_toolVolume;
|
||||
float m_blockVolume;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
69
source/game/items/StarUnlockItem.cpp
Normal file
69
source/game/items/StarUnlockItem.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#include "StarUnlockItem.hpp"
|
||||
#include "StarPlayer.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarClientContext.hpp"
|
||||
#include "StarPlayerBlueprints.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
UnlockItem::UnlockItem(Json const& config, String const& directory, Json const& itemParameters)
|
||||
: Item(config, directory, itemParameters), SwingableItem(config) {
|
||||
m_tierRecipesUnlock = instanceValue("tierRecipesUnlock").optString();
|
||||
m_shipUpgrade = instanceValue("shipUpgrade").optUInt();
|
||||
m_unlockMessage = instanceValue("unlockMessage").optString().value();
|
||||
auto image = AssetPath::relativeTo(directory, instanceValue("image").toString());
|
||||
m_drawables = {Drawable::makeImage(image, 1.0f / TilePixels, true, Vec2F())};
|
||||
}
|
||||
|
||||
ItemPtr UnlockItem::clone() const {
|
||||
return make_shared<UnlockItem>(*this);
|
||||
}
|
||||
|
||||
List<Drawable> UnlockItem::drawables() const {
|
||||
return m_drawables;
|
||||
}
|
||||
|
||||
List<Drawable> UnlockItem::preview(PlayerPtr const& viewer) const {
|
||||
return iconDrawables();
|
||||
}
|
||||
|
||||
void UnlockItem::fireTriggered() {
|
||||
if (!initialized())
|
||||
throw ItemException("Item not init'd properly, or user not recognized as Tool User.");
|
||||
|
||||
// Only the player can use an unlock item, for any other entity it should do
|
||||
// nothing.
|
||||
if (auto player = as<Player>(owner())) {
|
||||
if (instanceValue("consume", true).toBool() && !consume(1))
|
||||
return;
|
||||
|
||||
if (auto clientContext = player->clientContext()) {
|
||||
if (m_shipUpgrade)
|
||||
clientContext->rpcInterface()->invokeRemote("ship.applyShipUpgrades", JsonObject{{"shipLevel", *m_shipUpgrade}});
|
||||
}
|
||||
|
||||
if (!m_unlockMessage.empty()) {
|
||||
JsonObject message;
|
||||
message["message"] = m_unlockMessage;
|
||||
owner()->interact(InteractAction(InteractActionType::ShowPopup, owner()->entityId(), message));
|
||||
}
|
||||
|
||||
if (m_tierRecipesUnlock) {
|
||||
auto playerConfig = Root::singleton().assets()->json("/player.config");
|
||||
|
||||
List<ItemDescriptor> blueprints;
|
||||
for (Json v : playerConfig.get("defaultBlueprints", JsonObject()).getArray(*m_tierRecipesUnlock, JsonArray()))
|
||||
blueprints.append(ItemDescriptor(v));
|
||||
|
||||
auto speciesConfig = Root::singleton().assets()->json(strf("/species/%s.species", player->species()));
|
||||
for (Json v : speciesConfig.get("defaultBlueprints", JsonObject()).getArray(*m_tierRecipesUnlock, JsonArray()))
|
||||
blueprints.append(ItemDescriptor(v));
|
||||
|
||||
for (auto b : blueprints)
|
||||
player->addBlueprint(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
35
source/game/items/StarUnlockItem.hpp
Normal file
35
source/game/items/StarUnlockItem.hpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef STAR_CELESTIAL_ITEM_HPP
|
||||
#define STAR_CELESTIAL_ITEM_HPP
|
||||
|
||||
#include "StarItem.hpp"
|
||||
#include "StarWorld.hpp"
|
||||
#include "StarSwingableItem.hpp"
|
||||
#include "StarPreviewableItem.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(UnlockItem);
|
||||
|
||||
class UnlockItem : public Item, public SwingableItem, public PreviewableItem {
|
||||
public:
|
||||
UnlockItem(Json const& config, String const& directory, Json const& itemParameters = JsonObject());
|
||||
|
||||
ItemPtr clone() const override;
|
||||
|
||||
List<Drawable> drawables() const override;
|
||||
List<Drawable> preview(PlayerPtr const& viewer = {}) const override;
|
||||
|
||||
protected:
|
||||
void fireTriggered() override;
|
||||
|
||||
private:
|
||||
Maybe<String> m_sectorUnlock;
|
||||
Maybe<String> m_tierRecipesUnlock;
|
||||
Maybe<unsigned> m_shipUpgrade;
|
||||
String m_unlockMessage;
|
||||
List<Drawable> m_drawables;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue