블로그로 돌아가기
레이드계산기데이터러스트

유황 2000개로 뭘 부술 수 있을까? - 레이드 계산기

러스트 레이드 비용 계산기 만들면서 겪은 데이터 수집, 계산 로직, 엣지 케이스 삽질기

Softopia
2024년 12월 4일
8 min read

발단: "금속 문 부수려면 뭐가 필요해?"

디스코드에서 매일 같은 질문이 왔다.

"로켓 몇 개면 금속 벽 부서져요?" "C4랑 폭발탄 뭐가 더 효율적이에요?" "유황 3000개 있는데 어디까지 뚫을 수 있어요?"

러스트 레이드는 비용이 엄청나다. 유황 수천 개가 한순간에 사라진다. 잘못된 무기 선택하면 자원 낭비하고, 최적 조합 알면 적은 자원으로 더 많이 부술 수 있다.

"계산기 만들면 되겠네"

그렇게 1,200줄짜리 데이터 파일과 씨름이 시작됐다.


데이터 수집: 직접 정리

처음엔 데이터를 어디서 가져올지 고민했다.

RustClash Wiki에서 데이터를 참고했다. 각 건축물의 Durability 탭에 나와있는 최적 유황 효율(Cheapest Sulfur) 기준으로 정리.


데이터 구조 설계

// 파괴 대상
interface RaidTarget {
  id: string;
  name: { ko: string; ja: string };
  category: 'building' | 'deployable';
  hp: number;
  costs: RaidCost[];
  weakSide?: 'soft' | 'hard';
}

// 각 무기별 파괴 비용
interface RaidCost {
  weapon: WeaponType;
  quantity: number;
  sulfur: number;  // 총 유황 비용
  time: number;    // 파괴 시간 (초)
}

// 무기 종류
type WeaponType = 
  | 'c4'           // C4 폭발물
  | 'rocket'       // 로켓
  | 'satchel'      // 폭약 가방
  | 'explo_ammo'   // 폭발 탄약
  | 'incendiary'   // 화염 로켓
  | 'fire_arrow';  // 불화살

왜 유황 비용을 따로 저장하나? C4 1개에 유황 2200개, 로켓 1개에 1400개... 무기마다 효율이 다르다.


1,200줄의 데이터 파일

게임 내에서 직접 테스트하면서 데이터를 모았다. 2주 걸림.

// raidData.ts
export const RAID_TARGETS: RaidTarget[] = [
  {
    id: 'stone-wall',
    name: { ko: '돌 벽', ja: '石の壁' },
    category: 'building',
    hp: 500,
    weakSide: 'soft',  // 소프트 사이드가 약함
    costs: [
      { weapon: 'c4', quantity: 2, sulfur: 4400, time: 10 },
      { weapon: 'rocket', quantity: 4, sulfur: 5600, time: 12 },
      { weapon: 'satchel', quantity: 10, sulfur: 4800, time: 40 },
      { weapon: 'explo_ammo', quantity: 185, sulfur: 4625, time: 90 },
    ]
  },
  {
    id: 'metal-wall',
    name: { ko: '금속 벽', ja: '金属の壁' },
    category: 'building',
    hp: 1000,
    costs: [
      { weapon: 'c4', quantity: 4, sulfur: 8800, time: 20 },
      { weapon: 'rocket', quantity: 8, sulfur: 11200, time: 24 },
      // ...
    ]
  },
  // ... 100개 이상의 대상
];

엣지 케이스들

1. 소프트 사이드 vs 하드 사이드

돌/금속 건물은 방향이 있다. 안쪽(소프트)이 약하고 바깥쪽(하드)이 강하다.

{
  id: 'stone-wall',
  weakSide: 'soft',
  costs: {
    hard: [/* 하드 사이드 비용 */],
    soft: [/* 소프트 사이드 비용 (더 적음) */]
  }
}

처음엔 이거 무시하고 하드 사이드 기준으로만 했다가 유저한테 욕먹음. "벽 안쪽 치면 절반인데요?"

2. 콤보 공격

C4로 문 폭파하고 남은 체력은 폭발탄으로 마무리하면 더 효율적인 경우가 있다.

// 예: 금속 문
// C4 1개 (2200 유황) + 폭발탄 63발 (1575 유황) = 3775 유황
// vs C4 2개 = 4400 유황
// 625 유황 절약!

이런 콤보를 어떻게 계산하지?

처음엔 무시했다. "그냥 무기 하나씩만 계산합시다"

근데 유저들이 계속 물어봤다. 결국 콤보 계산기 따로 만들었다.

3. 불 데미지

화염 로켓, 불화살은 나무 건물에만 효과적이다. 금속엔 안 먹힘.

// 불 무기는 특정 재질에만 사용 가능
if (target.material === 'wood') {
  costs.push({ weapon: 'incendiary', ... });
}

UI 구현: 양방향 계산

두 가지 사용 패턴:

  1. "이거 부수려면 뭐가 필요해?": 대상 선택 → 필요 자원 표시
  2. "유황 2000개로 뭘 할 수 있어?": 보유 자원 입력 → 파괴 가능한 대상 표시
// 대상 → 자원 (Forward)
function getRequiredResources(target: string, weapon: WeaponType) {
  const data = RAID_TARGETS.find(t => t.id === target);
  const cost = data?.costs.find(c => c.weapon === weapon);
  return cost;
}

// 자원 → 대상 (Reverse)
function getDestroyableTargets(sulfur: number) {
  return RAID_TARGETS
    .map(target => ({
      target,
      bestOption: target.costs
        .filter(c => c.sulfur <= sulfur)
        .sort((a, b) => a.sulfur - b.sulfur)[0]
    }))
    .filter(r => r.bestOption);
}

역방향 계산이 더 복잡했다. "유황 5000개로 금속 문 몇 개 부술 수 있어?"

function calculateMaxTargets(sulfur: number, targetId: string) {
  const target = RAID_TARGETS.find(t => t.id === targetId);
  const cheapest = target?.costs.sort((a, b) => a.sulfur - b.sulfur)[0];
  
  if (!cheapest) return 0;
  
  return Math.floor(sulfur / cheapest.sulfur);
}

// 유황 5000개 → 금속 문 최대 1개 (C4 2개 = 4400 유황)
// 남는 유황 600개

실시간 집계 기능

레이드 계획 세울 때 여러 대상을 동시에 계산해야 한다.

"벽 3개, 문 2개, 공구함 1개 부수려면 총 얼마?"

장바구니처럼 구현했다.

interface RaidPlan {
  items: { targetId: string; count: number; weapon: WeaponType }[];
}

function calculateTotalCost(plan: RaidPlan): TotalCost {
  let totalSulfur = 0;
  const weaponCounts: Record<WeaponType, number> = {};
  
  for (const item of plan.items) {
    const target = RAID_TARGETS.find(t => t.id === item.targetId);
    const cost = target?.costs.find(c => c.weapon === item.weapon);
    
    if (cost) {
      totalSulfur += cost.sulfur * item.count;
      weaponCounts[item.weapon] = 
        (weaponCounts[item.weapon] || 0) + cost.quantity * item.count;
    }
  }
  
  return { totalSulfur, weaponCounts };
}
레이드 계획:
- 금속 벽 x 3 (로켓) → 33,600 유황
- 금속 문 x 2 (C4) → 4,400 유황
- 공구함 x 1 (폭발탄) → 200 유황
─────────────────────────
총합: 38,200 유황
무기 필요량: 로켓 24발, C4 2개, 폭발탄 8발

최적화 추천 기능

유저가 무기를 직접 선택하게 하면 비효율적인 선택을 한다. 자동으로 최적 무기를 추천해주기로.

function recommendWeapon(targetId: string): WeaponType {
  const target = RAID_TARGETS.find(t => t.id === targetId);
  if (!target) throw new Error('Unknown target');
  
  // 유황 효율이 가장 좋은 무기 선택
  const sorted = [...target.costs].sort((a, b) => a.sulfur - b.sulfur);
  return sorted[0].weapon;
}

근데 문제가 있었다.

시간 효율 vs 자원 효율

폭발탄은 유황 효율 좋지만 시간이 오래 걸린다. C4는 비싸지만 빠르다.

금속 벽:
- 폭발탄: 4625 유황, 90초
- C4: 4400 유황, 10초  ← 더 싸고 빠름!

옵션을 추가했다:

type OptimizeFor = 'sulfur' | 'time' | 'balanced';

function recommendWeapon(targetId: string, optimizeFor: OptimizeFor) {
  const target = RAID_TARGETS.find(t => t.id === targetId);
  
  if (optimizeFor === 'sulfur') {
    return target.costs.sort((a, b) => a.sulfur - b.sulfur)[0];
  }
  
  if (optimizeFor === 'time') {
    return target.costs.sort((a, b) => a.time - b.time)[0];
  }
  
  // balanced: 유황과 시간 가중 평균
  return target.costs.sort((a, b) => {
    const scoreA = a.sulfur * 0.7 + a.time * 30 * 0.3;
    const scoreB = b.sulfur * 0.7 + b.time * 30 * 0.3;
    return scoreA - scoreB;
  })[0];
}

데이터 유지보수 문제

러스트는 업데이트가 자주 있다. 무기 데미지 바뀌면 데이터 전부 수정해야 한다.

문제 상황

2024년 12월 패치에서 폭발탄 데미지가 너프됐다.

- 폭발탄: 벽당 185발 필요
+ 폭발탄: 벽당 200발 필요

100개 넘는 대상 전부 수정해야 함.

해결: 계산 기반 데이터 생성

무기 데미지와 대상 체력만 저장하고, 필요 수량은 계산하기로.

// 기본 데이터 (패치 때 이것만 수정)
const WEAPON_DAMAGE = {
  c4: 550,
  rocket: 350,
  satchel: 475,
  explo_ammo: 8,  // 패치로 변경됨
};

const TARGET_HP = {
  'stone-wall': 500,
  'metal-wall': 1000,
  // ...
};

// 필요 수량은 자동 계산
function calculateQuantity(targetId: string, weaponId: string): number {
  const hp = TARGET_HP[targetId];
  const damage = WEAPON_DAMAGE[weaponId];
  return Math.ceil(hp / damage);
}

문제: 데미지가 단순히 hp / damage가 아님. 방어력, 저항, 스플래시 등 복잡한 공식 존재.

결론: 일부만 계산 기반으로, 나머지는 수동 데이터 유지.


실패한 시도들

1. 크래프팅 재료까지 계산

C4 만들려면 화약, 유황, 금속 조각... 원재료까지 역산하려고 했다.

// C4 1개 제작 원재료
// 화약 1000개 → 유황 2000개 + 숯 3000개
// 금속 조각 200개
// 기술 폐품 2개
// 천 5개

포기 이유:

  • 복잡도 폭발. 원재료의 원재료의 원재료...
  • 유저마다 보유 재료 다름
  • UI가 너무 복잡해짐

결국 "유황 기준"으로 통일. 유황이 가장 부족한 자원이니까.

2. 건물 구조 시뮬레이터

"이 베이스 레이드하려면 총 얼마?"

베이스 구조를 그리면 자동으로 최적 경로 계산.

포기 이유:

  • 베이스 에디터 만드는 게 본업보다 오래 걸림
  • 이미 Fortify라는 전문 툴이 있음

현재 기능

  1. 대상별 비용 조회: 건물, 문, 공구함, 차량 등 100+ 대상
  2. 무기별 비교: 같은 대상을 다른 무기로 파괴할 때 비용 비교
  3. 레이드 계획: 여러 대상 선택 후 총 비용 계산
  4. 역계산: 보유 유황으로 파괴 가능한 대상 확인
  5. 콤보 계산: 무기 조합 최적화

사용 통계

| 항목 | 수치 | |------|------| | 월 계산 횟수 | ~8,000회 | | 가장 많이 조회하는 대상 | 금속 문 | | 가장 인기 있는 무기 | C4 | | 평균 레이드 계획 항목 수 | 4개 |

금속 문이 1위인 건 예상했다. 베이스 침입의 첫 관문이니까.


배운 것

  1. 데이터가 핵심: 알고리즘보다 데이터 품질이 중요
  2. 엣지 케이스가 본체: 소프트 사이드, 콤보 같은 예외가 기능의 80%
  3. 유지보수 고려: 게임 업데이트마다 데이터 바꿔야 한다는 걸 처음부터 고려
  4. 유저는 게으르다: 자동 추천 없으면 아무도 최적화 안 함

남은 과제

  • [ ] 건물 구조 입력 (Fortify 연동?)
  • [ ] 패치 노트 자동 파싱해서 데이터 업데이트 알림
  • [ ] 레이드 결과 공유 기능 (URL로)

다음 편에서는 리더보드와 MMR 시스템. "이 서버에서 킬 1위는 누구?"를 실시간으로 보여주는 방법.


질문 있으면 디스코드로: softopia