概要
リワードなどに使えます
環境
- macOS 15.2
- Java 21.0.5
- forrge MDK 1.20.6-50.1.32
- minecraft 1.20.6
サンプルコード
package com.example.examplemod;
import java.util.List;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.toasts.SystemToast;
import net.minecraft.client.gui.components.toasts.ToastComponent;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.ProjectileUtil;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
public class LaserGunItem extends Item {
private float amountOfdamage = 100.0F; // ダメージ量
private String countKey = "laserGunKills"; // カウントのキー
private int previousCount = 0;
private boolean isLocalSent = false;
public LaserGunItem(Properties properties) {
super(properties);
}
@Override
public InteractionResultHolder<ItemStack> use(
Level world, Player player, InteractionHand hand) {
// クライアント側のみでエフェクトを表示
if (world.isClientSide()) {
// ビームエフェクトを生成
spawnLaserBeam(player);
if (isLocalSent) {
sendToastMessage(player, previousCount);
isLocalSent = false;
}
} else {
// サーバー側でダメージ処理
fireLaser(world, player);
// 討伐数の取得
CompoundTag tag = player.getPersistentData(); // プレイヤーのデータを取得
int currentCount = tag.getInt(countKey);
// 前の値と比較して進んでいたら判定に進む、previousCountが0の場合は起動後まだ一度も倒していない状態なので判定しない
if (previousCount != 0 && previousCount < currentCount) {
// クライアント用の変数にも登録
if (currentCount % 10 == 0) {
sendChatMessage(player, currentCount);
spawnReward(player, world);
// ローカルの通知も許可する、このロジックだと一発分遅れる
isLocalSent = true;
}
}
// 一つ前の値を保存
previousCount = currentCount;
}
return InteractionResultHolder.sidedSuccess(
player.getItemInHand(hand), world.isClientSide());
}
@Override
public void appendHoverText(
ItemStack stack,
Item.TooltipContext context,
List<Component> tooltip,
TooltipFlag flag) {
super.appendHoverText(stack, context, tooltip, flag);
// ダメージ情報を追加
tooltip.add(
Component.translatable("item.examplemod.laser_gun.damage", amountOfdamage)
.withStyle(ChatFormatting.GREEN));
// 他の情報も追加可能
tooltip.add(
Component.translatable("item.examplemod.laser_gun.description")
.withStyle(ChatFormatting.GRAY));
}
@Override
public boolean onEntityItemUpdate(ItemStack stack, ItemEntity entity) {
// 特殊な処理が必要であればここで行う(今回は不要なのでデフォルト動作に任せます)
// アイテムが q で捨てられたときにアイテムとして残すための設定
return super.onEntityItemUpdate(stack, entity);
}
private void spawnLaserBeam(Player player) {
// クライアント側エフェクト(例:パーティクル)
player.level()
.addParticle(
ParticleTypes.END_ROD,
player.getX(),
player.getEyeY(),
player.getZ(),
player.getLookAngle().x,
player.getLookAngle().y,
player.getLookAngle().z);
}
private void fireLaser(Level world, Player player) {
// 音の再生
if (world instanceof ServerLevel serverLevel) {
serverLevel.playSound(
null, // プレイヤー(nullにすると全員が聞こえる)
player.blockPosition(), // 再生位置
ExampleMod.LASER_FIRE_SOUND.get(), // サウンドイベント
SoundSource.PLAYERS, // サウンドの種類(PLAYERS, BLOCKS, AMBIENT など)
1.0f, // 音量
1.0f // 音の高さ(ピッチ)
);
}
// 発射用の座標と距離の設定
Vec3 start = player.getEyePosition(1.0F);
Vec3 direction = player.getLookAngle();
double range = 50.0D; // 50ブロック先をターゲット
Vec3 end = start.add(direction.scale(range));
// ブロックとのヒット判定
HitResult blockHit =
world.clip(
new ClipContext(
start,
end,
ClipContext.Block.OUTLINE,
ClipContext.Fluid.NONE,
player));
// エンティティとのヒット判定
EntityHitResult entityHit =
ProjectileUtil.getEntityHitResult(
world,
player,
start,
end,
player.getBoundingBox().expandTowards(direction.scale(range)).inflate(1.0D),
entity -> entity instanceof LivingEntity && entity != player);
// 優先的にエンティティを処理
if (entityHit != null) {
System.out.println("Hit entity!");
Entity hitEntity = entityHit.getEntity();
if (hitEntity instanceof LivingEntity livingEntity) {
// 敵にダメージを与える
livingEntity.hurt(
livingEntity.damageSources().playerAttack(player), amountOfdamage);
// 倒したか確認
if (livingEntity.isDeadOrDying()) {
// カウントを増やす
incrementKillCount(player);
}
}
} else if (blockHit != null && blockHit.getType() == HitResult.Type.BLOCK) {
// ブロックにヒットした場合
System.out.println("Hit block at: " + ((BlockHitResult) blockHit).getBlockPos());
BlockHitResult blockResult = (BlockHitResult) blockHit;
BlockPos hitPos = blockResult.getBlockPos();
// ヒット位置に火をつける例
world.setBlockAndUpdate(hitPos, Blocks.FIRE.defaultBlockState());
} else {
// 何にも当たらなかった場合
System.out.println("Missed!");
}
}
private void incrementKillCount(Player player) {
CompoundTag tag = player.getPersistentData(); // プレイヤーのデータを取得
int currentCount = tag.getInt(countKey); // 現在のカウント
tag.putInt(countKey, currentCount + 1); // カウントを増加
}
private void sendChatMessage(Player player, int currentCount) {
player.sendSystemMessage(
Component.translatable(
"The player %s killed %d mobs with laser gun."
.formatted(player.getName().getString(), currentCount))
.withStyle(ChatFormatting.GREEN));
}
private void sendToastMessage(Player player, int currentCount) {
String title = "Good job!";
String description = "Killed %d mobs with laser gun.".formatted(currentCount);
Minecraft minecraft = Minecraft.getInstance();
ToastComponent toastComponent = minecraft.getToasts();
// トーストを作成
SystemToast toast =
new SystemToast(
SystemToast.SystemToastId.PERIODIC_NOTIFICATION, // トーストのタイプ(任意の値でOK)
Component.literal(title), // タイトル
Component.literal(description) // 説明
);
// トーストを表示
toastComponent.addToast(toast);
}
// 報酬を降らせる処理
private void spawnReward(Player player, Level level) {
// プレイヤーの位置
double x = player.getX();
double y = player.getY() + 20; // プレイヤーの上空20ブロック
double z = player.getZ();
for (int i = 0; i < 10; ++i) {
// 報酬アイテム(例: ダイヤモンド)
ItemEntity reward = new ItemEntity(level, x, y, z, Items.DIAMOND.getDefaultInstance());
level.addFreshEntity(reward);
}
}
}
ポイント
level.addFreshEntity
で指定の座標にアイテムを発生させることができます
// 報酬を降らせる処理
private void spawnReward(Player player, Level level) {
// プレイヤーの位置
double x = player.getX();
double y = player.getY() + 20; // プレイヤーの上空20ブロック
double z = player.getZ();
for (int i = 0; i < 10; ++i) {
// 報酬アイテム(例: ダイヤモンド)
ItemEntity reward = new ItemEntity(level, x, y, z, Items.DIAMOND.getDefaultInstance());
level.addFreshEntity(reward);
}
}
またアイテムの生成はサーバ側で行います
@Override
public InteractionResultHolder<ItemStack> use(
Level world, Player player, InteractionHand hand) {
// クライアント側のみでエフェクトを表示
if (world.isClientSide()) {
// ビームエフェクトを生成
spawnLaserBeam(player);
if (isLocalSent) {
sendToastMessage(player, previousCount);
isLocalSent = false;
}
} else {
// サーバー側でダメージ処理
fireLaser(world, player);
// 討伐数の取得
CompoundTag tag = player.getPersistentData(); // プレイヤーのデータを取得
int currentCount = tag.getInt(countKey);
// 前の値と比較して進んでいたら判定に進む、previousCountが0の場合は起動後まだ一度も倒していない状態なので判定しない
if (previousCount != 0 && previousCount < currentCount) {
// クライアント用の変数にも登録
if (currentCount % 10 == 0) {
sendChatMessage(player, currentCount);
spawnReward(player, world);
// ローカルの通知も許可する、このロジックだと一発分遅れる
isLocalSent = true;
}
}
// 一つ前の値を保存
previousCount = currentCount;
}
return InteractionResultHolder.sidedSuccess(
player.getItemInHand(hand), world.isClientSide());
}
最後に
クライアント側とサーバ側で行う処理を混同させないようにしましょう
基本的にはクライアント側はそのユーザのみでサーバ側は全ユーザに対して行う処理を記載します
現状はカスタムアイテム側でダメージ判定や討伐数管理をしていますがワールド側のイベントハンドリングで敵が onLivingDeath などで敵が倒されたときの武器を判定して特定の武器であれば討伐数をカウントするなどの実装方法もあります
そのあたりの実装方法は経験に左右されるところなのでなかなか難しいかなと思います
0 件のコメント:
コメントを投稿