세라봇 & Web

낚시게임 본문

카톡봇 프로그래밍/소스

낚시게임

Kkyuya 2021. 1. 4. 14:03
※ 낚시게임 특징/설명
- FileStream을 사용하여 유저정보 개별저장
- 낚시 실패 시 여러가지 실패문구 출력
- Math.random() 함수를 사용하여 확률별로 무작위출력
- 낚싯대를 뽑아 물고기 낚을확률 증가 및 감소
- 낚시실패 시 체력감소, 성공 시 체력/미끼 감소
- 낚시성공 일정수치 도달 시 랭크업
- 랭크 상승 시 최대체력/낚시성공률 증가, 낚시시간/회복딜레이 감소
- 모든유저통합 낚시랭킹 출력
- 물고기 잠금/잠금해제 기능(잠금된 물고기는 판매불가)
- "잠금명령어 5-10" 등으로 잠금/잠금해제할 물고기 연속선택 가능

 

첨부된 fishing_data.js 파일을 "메신저봇폴더/global_modules" 폴더에 넣어주세요.

fishing_data.js
0.01MB

사용 전에 먼저 "..낚시랭킹" 명령어를 적어서 랭킹파일 생성완료메시지를 확인 후 사용해주세요.

  • 전역에서 수정할 수 있도록 편의성을 생각하여 제작했습니다.
  • 주석내용을 꼼꼼히 읽어보시고 사용해주세요.
  • 초보라면 수정하라고 표기된 것만 수정하세요.
  • 오류 발생 시 댓글에 적어주세요. <오류내용, 오류발생소스 첨부>

/*
물고기 낚을 확률을 잘못 수정하면 유저정보 표시에 문제가 생깁니다.
모든 주석을 꼼꼼히 읽어본 후 사용하시기 바랍니다.
명령어 및 수치설정은 첨부된 fishing_data.js 파일에 있습니다.
http://kkyuya.tistory.com
*/
const fishData=require('fishing_data');
const ii = "━━━━━━━━━━";
const ll = "━━━━━━━━━━━━━━";
const li = "━━━━━━━━━━━━━━━━━━━━━━━━";
const more = "​".repeat(500);
const path='sdcard/Fishing/user/';  //유저경로
const pathRank='sdcard/Fishing/rank.json';  //랭킹파일경로
const FS=FileStream;
var fishUser={};  //낚시유저 데이터
var fishInv={};
var fishdelay={};  //낚시 소요시간
var fishOn={};  //낚시 On/Off

/* 천단위함수 */
function Div(str) {
    str = String(str);
    str = str.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    return str;
}

const fishfail=fishData.fishfail;
const rodArr=fishData.rodArr;
const baitArr=fishData.baitArr;
const fishArr=fishData.fishArr;
const fishex=fishData.fishex;
const nofishArr=fishData.trashex;
const fishcmd=fishData.fishcmd;
const config=fishData.config;

/* 랭크업 함수 */
rankup=(a)=>{
    for(let i=config.rank.name.length-1; i>-1; i--){
        if(a.count.true>=config.rank.if[i]){
        a.rank=config.rank.name[i];
        a.successRate=config.rank.success[i];
        a.maxHp=config.rank.maxHp[i];
        a.rest=config.rank.rest[i];
        a.castT=config.rank.castT[i];
        break;
        }
    }
};

/* 업데이트 함수 */
update=(a)=>{
    if(a.count.true+a.count.fail!=a.count.total) a.count.total=Number(a.count.true)+Number(a.count.fail);
};


function response(r, msg, s, isGroupChat, replier, imageDB) {

/* 도움말 */
if(msg==fishcmd.help){
    replier.reply([
    '🎣 세라 낚시터 명령어'+more,li,
    '※ 낚시성공횟수가 일정횟수에 도달하면 칭호가 변경되요.',
    '※ 칭호Up :: 낚시성공률 증가, 휴식시간/낚시소요시간 감소',
    '※ 낚싯대 :: 쓰레기/물고기 낚을 확률에 영향',
    '※ 미끼 :: 거대어종 낚을 확률에 영향',li,
    fishcmd.uphelp+' : 낚시 시스템 도움말',
    fishcmd.join+' : 세라낚시터 회원등록',
    fishcmd.info+' : 나의 낚시정보 보기',
    fishcmd.box+' : 잡은 물고기 보관통',
    fishcmd.info+' @이름 : 상대방의 낚시정보 보기',li,
    fishcmd.play+' : 낚시하기',
    fishcmd.rest+' : 체력회복 (토글)',
    fishcmd.lock+' (숫자) : 판매하지 않을 물고기 선택',
    fishcmd.unlock+' (숫자) : 물고기 잠금 해제',
    '( ※ 연속선택 예시 ..잠금 2-8 : 2~8번 모두잠금 )',li,
    fishcmd.sell+' : 잠금안된 물고기 모두 판매',
    fishcmd.rodpop+' : 낚싯대 뽑기 ('+Div(config.rodpopPrice)+'골드)',
    fishcmd.rodnamepop+' : 낚싯대뽑기, 능력치고정 ('+Div(config.rodnamePrice)+'골드)',
    fishcmd.rodup+' : 낚싯대 강화(골드소모)',
    fishcmd.bait+' (수량) : 미끼구매',
    fishcmd.baitup+' : 미끼 강화(골드,성공횟수 소모)',li,
    fishcmd.rank+' : 낚시터 순위 보기',
    fishcmd.title+' : 낚시터 칭호 목록',li,
    fishcmd.leave+' : 세라낚시터 회원해지'
    ].join('\n'));
    return;
}

if(msg==fishcmd.uphelp){
    replier.reply([
        '🐬 세라 낚시터 도움말'+more,li,
        '※ 낚시터 명령어는 "'+fishcmd.help+'"에서 확인하세요.',,
        ll,'√ 낚싯대 뽑기',ll,
        '• 뽑기의 최초 가격은 '+Div(config.rodpopPrice)+'골드예요.',
        '• 뽑기를 거듭할 수록 가격이 '+Div(config.rodpopPriceCoe)+'골드씩 늘어나요.',
        '• 낚싯대 이름과 능력치는 별개예요.',
        '• 원하는 능력치가 나왔다면 이름뽑기로 이름만 바꿀 수 있어요.',,
        ll,'√ 낚싯대 강화',ll,
        '• 낚싯대를 강화하여 낚을확률을 영구적으로 올려요.',
        '• 레벨당 중어이상 물고기 낚을확률이 소폭 증가',
        '• 반대로 소형어종을 낚을확률은 줄어들어요.',
        '• 강화비용은 "낚싯대레벨x'+Div(config.rodupPrice)+'골드" 예요.',
        '• 현재 최대레벨은 Lv.'+config.maxRodup+' 이예요.',
        '• 낚싯대를 다시 뽑아도 강화레벨이 유지되요.',,
        ll,'√ 미끼 강화',ll,
        '• 미끼를 다른미끼로 강화해요.',
        '• 대어이상 낚을 확률이 대폭 증가해요',
        '• 일정량의 골드와 낚시성공횟수가 필요해요.',
        '• 강화하면 미끼의 가격이 대폭증가해요.',,
        ll,'√ 칭호 시스템',ll,
        '• 낚시성공횟수에 따라 칭호가 자동적용되요.',
        '• 낚시성공확률, 낚시시간, 휴식시간이 바뀌어요.',
        '• "..낚시칭호" 명령어로 자세한 내용을 확인해보세요.'
    ].join('\n'));
}

/* 낚시터 입장, 등록 */
if(msg==fishcmd.join){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        if(s.includes('/')){
            replier.reply('@'+s+' \n• 닉네임에 슬래시"/" 가 들어가면 가입할 수 없어요.');
            return;
        }
        if(s.length<1){
            replier.reply('@'+s+' \n• 닉네임이 공백이면 가입할 수 없어요.');
            return;
        }
        let data={
            'name':s,
            'hp':config.rank.maxHp[0], 'maxHp':config.rank.maxHp[0],
            'best':{
                'name':'없음',
                'cm':0
            },
            'item':'낡은낚싯대',
            'rodpop':0,  //낚싯대 뽑은 횟수
            'lv':1,  //낚싯대 레벨
            'stat':{
                'xxl':0, 'xl':0, 'l':0, 'm':0, 'trash':0  //낚싯대 능력치
            },
            'rank':config.rank.name[0],  //칭호
            'gold':1000,  //초기보유자금
            'Bait':baitArr[0],  //미끼이름
            'bait':50, //초기미끼갯수
            'count':{
                'total':0,  //낚시횟수
                'true':0,  //낚시 성공횟수
                'fail':0  //낚시 실패횟수
            },
            'successRate':0,  //낚시성공률
            'rest':Number(config.rank.rest[0]),  //체력+1당 휴식시간
            'castT':0,  //캐스팅시간
            'restOn':{'on':0, 'time':0}  //휴식객체
        };
        let inv={'lock':[],'fish':[]};
        FS.write(path+s+"/"+s+'.json', JSON.stringify(data));
        FS.write(path+s+'/inv.json', JSON.stringify(inv));
        replier.reply('@'+s+' \n• 낚시터에 오신것을 환영해요!\n• "..낚시" 명령어로 낚싯대를 던져보세요.');
    } else replier.reply('@'+s+' \n• 이미 낚시터 회원이예요.');
}

/* 낚시터 퇴장, 탈퇴 */
if(msg==fishcmd.leave){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null) replier.reply('@'+s+' \n• 낚시터에 등록된 분이 아니예요.');
    else{
        let fishRank=JSON.parse(FS.read(pathRank));
        if(fishRank.some(e=>e.name==s)){
            fishRank.splice(fishRank.findIndex(e=>e.name==s),1);
            FS.write(pathRank, JSON.stringify(fishRank));
        }
        FS.remove(path+s+"/"+s+'.json');
        FS.remove(path+s+'/.json');
        replier.reply('@'+s+' \n• 낚시터 주인에게 발각되어 쫓겨났어요.\n• 낚시정보 삭제 완료.');
    }
    return;
}

/* 낚시 하기 */
if(fishcmd.play.includes(msg)){
    /* 디바이스 온도 체크 */
    if(Device.getBatteryTemperature()>=400){
        replier.reply('@'+s+' \n• 세라가 과열되었어요!\n• 식은 후 다시 시도해 주세요.');
        return;
    }
    /* 쓰레드감지 */
    if(Api.getActiveThreadsCount()>=5){
        replier.reply('@'+s+' \n• 사용자 폭주중이예요.\n• 잠시후에 다시 시도해 주세요.');
        return;
    }
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    if(fishOn[s]==undefined) fishOn[s]=0;
    if(fishOn[s]){
        replier.reply('@'+fishUser[s].name+'\n• 낚시중이예요. 기다리세요.');
        return;
    }
    update(fishUser[s]);
    if(fishUser[s].restOn.on){
        replier.reply('@'+s+' \n• 휴식중에는 낚시를 할 수 없어요.\n• "'+fishcmd.rest+'" 을 입력해서 종료하세요.');
        return;
    }
    if(fishUser[s].hp<=0){
        replier.reply('@'+s+' \n• 체력이 부족해요.\n• "'+fishcmd.rest+'" 명령어를 사용해보세요.');
        return;
    }
    if(fishUser[s].bait<=0){
        replier.reply('@'+s+' \n• 미끼가 없어요.\n• "'+fishcmd.bait+' (수량)" 명령어를 사용해보세요.');
        return;
    }
    fishOn[s]=1;
    fishInv[s]=JSON.parse(FS.read(path+s+'/inv.json'));
    fishdelay[s]=Math.random()*(config.castT.max*1000-config.castT.min*1000)+config.castT.min*1000-Number(fishUser[s].castT);  //낚시 소요시간
    let ltt=fishUser[s].item.length-1;
    replier.reply('• '+'<'+fishUser[s].rank+'> '+
        fishUser[s].name+'님이 '+fishUser[s].item+
        (fishUser[s].item[ltt].normalize("NFD").length == 3 ? '을' : '를')+' 던졌어요!');
    let rd=Math.random()*100;
    if(rd<=100-config.success-fishUser[s].successRate){
        ++fishUser[s].count.total;  //낚시횟수 증가
        ++fishUser[s].count.fail;
        --fishUser[s].hp;  //체력감소
        replier.reply('@'+fishUser[s].name+' \n'+
            fishfail[Math.random()*fishfail.length|0]+
            '\n• 낚시 실패!');
    }
    else{
        java.lang.Thread.sleep(fishdelay[s]);
        let fish;  //물고기이름
        let cmrd;  //물고기길이(number)
        let cm;  //물고기길이(string)
        let result;   //결과설명
        let baitCoe=Number(baitArr.indexOf(fishUser[s].Bait)*config.baitCoe);  //미끼로 인한 확률증가
        let rdfish=Math.random()*1000;
        if(rdfish<=(config.p.xxl*10+baitCoe+
            Number(fishUser[s].lv)-1+Number(fishUser[s].stat.xxl))){
            fish='<신화> '+fishArr.fish5[Math.random()*fishArr.fish5.length|0];
            cmrd=(Math.random()*294800+5200).toFixed(1);
            cm='('+cmrd+'Cm)';
            result=fishex.ex4[Math.random()*fishex.ex4.length|0];
        } else if(rdfish<=((config.p.xl+config.p.xxl)*10+baitCoe+
            Number(fishUser[s].lv)-1+Number(fishUser[s].stat.xxl)+Number(fishUser[s].stat.xl))){
            fish='<에픽> '+fishArr.fish4[Math.random()*fishArr.fish4.length|0];
            cmrd=(Math.random()*4500+700).toFixed(1);
            cm='('+cmrd+'Cm)';
            result=fishex.ex4[Math.random()*fishex.ex4.length|0];
        } else if(rdfish<=((config.p.l+config.p.xl+config.p.xxl)*10+baitCoe+
            Number(fishUser[s].lv)-1+Number(fishUser[s].stat.xxl)+Number(fishUser[s].stat.xl)+Number(fishUser[s].stat.l))){
            fish='<대물> '+fishArr.fish3[Math.random()*fishArr.fish3.length|0];
            cmrd=(Math.random()*600+100).toFixed(1);
            cm='('+cmrd+'Cm)';
            result=fishex.ex3[Math.random()*fishex.ex3.length|0];
        } else if(rdfish<=((config.p.m+config.p.l+config.p.xl+config.p.xxl)*10+
            Number(fishUser[s].lv)-1+Number(fishUser[s].stat.xxl)+Number(fishUser[s].stat.xl)+Number(fishUser[s].stat.l)+Number(fishUser[s].stat.m))){
            fish=fishArr.fish2[Math.random()*fishArr.fish2.length|0];
            cmrd=(Math.random()*85+15).toFixed(1);
            cm='('+cmrd+'Cm)';
            result=fishex.ex2[Math.random()*fishex.ex2.length|0];
        } else if(rdfish<=((config.p.s+config.p.m+config.p.l+config.p.xl+config.p.xxl)*10-
            Number(fishUser[s].stat.trash))){
            fish=fishArr.fish1[Math.random()*fishArr.fish1.length|0];
            cmrd=(Math.random()*14+1).toFixed(1);
            cm='('+cmrd+'Cm)';
            result=fishex.ex1[Math.random()*fishex.ex1.length|0];
        } else if(rdfish<=config.p.t){  //쓰레기 낚을확률
            fish=fishArr.trash[Math.random()*fishArr.trash.length|0];
            cmrd=0;
            cm='';
            result=nofishArr.ex1[Math.random()*nofishArr.ex1.length|0]+nofishArr.ex2[Math.random()*nofishArr.ex2.length|0];
        } else{
            fish=fishArr.gold[Math.random()*fishArr.gold.length|0];
            cmrd=Math.random()*1900+100|0;
            ++fishUser[s].count.total;  //낚시횟수 증가
            ++fishUser[s].count.true;  //성공횟수 증가
            --fishUser[s].hp;  //체력감소
            --fishUser[s].bait;  //미끼소모
            fishUser[s].gold=Number(fishUser[s].gold)+Number(cmrd);
            let lt=fish.length-1;
            FS.write(path+s+"/"+s+'.json', JSON.stringify(fishUser[s]));
            replier.reply('@'+s+' \n• '+fish+(fish[lt].normalize("NFD").length == 3 ? '을' : '를')+
                ' 낚았어요.\n• '+Div(cmrd)+'골드 획득.');
            fishdelay[s]=undefined;
            fishOn[s]=0;
            return;
        }
        ++fishUser[s].count.total;  //낚시횟수 증가
        ++fishUser[s].count.true;  //성공횟수 증가
        --fishUser[s].hp;  //체력감소
        --fishUser[s].bait;  //미끼소모
        let fishRank=JSON.parse(FS.read(pathRank));
        if(!fishRank.some(e=>e.name==fishUser[s].name)){
            let rfish={
                'name':fishUser[s].name,
                'rank':fishUser[s].rank,
                'fish':{'name':'없음','size':0}
            };
            if(cmrd!=0){
                rfish.fish.name=fish;
                rfish.fish.size=Number(cmrd);
            }
            fishRank.push(rfish);
            FS.write(pathRank, JSON.stringify(fishRank));
        } else{
            let n=fishRank.findIndex(e=>e.name==fishUser[s].name);
            if(Number(fishRank[n].fish.size)<Number(cmrd)){
                fishRank[n].rank=fishUser[s].rank;
                fishRank[n].fish.name=fish;
                fishRank[n].fish.size=Number(cmrd);
                FS.write(pathRank, JSON.stringify(fishRank));
            }
        }
        if(Number(fishUser[s].best.cm)<Number(cmrd)){  //최고기록 갱신
            fishUser[s].best.name=fish;
            fishUser[s].best.cm=Number(cmrd);
        }
        if(cmrd != 0){  //인벤토리에넣기
            let box={
                'name':fish,
                'size':cmrd,
                'gold':parseInt(cmrd*2)
            }
            fishInv[s].fish.push(box);
        }
        let lt=fish.length-1;
        replier.reply('@'+fishUser[s].name+' \n'+
        '• '+fish+Div(cm)+(fish[lt].normalize("NFD").length == 3 ? '을' : '를')+
        ' 낚았어요!\n• '+result);
    }
    rankup(fishUser[s]);
    FS.write(path+s+"/"+s+'.json', JSON.stringify(fishUser[s]));
    FS.write(path+s+'/inv.json', JSON.stringify(fishInv[s]));
    fishdelay[s]=undefined;
    fishOn[s]=0;
    return;
}

/* 낚시정보, 유저정보 함수 */
function UserInfo(fishuser){
    update(fishuser);
    let truep;  //낚시성공률
    let xl, l, m, trash  //스탯등급
    let st=fishuser.stat;
    if(st.xl>25) xl='최상';
    else if(st.xl>15) xl='상';
    else if(st.xl>3) xl='중';
    else xl='하';
    if(st.l>80) l='최상';
    else if(st.l>50) l='상';
    else if(st.l>20) l='중';
    else l='하';
    if(st.m>170) m='최상';
    else if(st.m>100) m='상';
    else if(st.m>20) m='중';
    else m='하';
    if(st.trash<=-120) trash='최상';
    else if(st.trash<=-70) trash='상';
    else if(st.trash<=0) trash='중';
    else trash='하';
    let p={'xxl':config.p.xxl*10,'xl':config.p.xl*10,'l':config.p.l*10,
        'm':config.p.m*10,'s':config.p.s*10,'t':config.p.t*10,'g':config.p.g*10};
    let baitCoe=Number(baitArr.indexOf(fishuser.Bait)*config.baitCoe);  //미끼로 인한 확률증가
    let sfish=p.s-Number(st.xxl)-Number(st.xl)-Number(st.l)-Number(st.m)+Number(st.trash)-(Number(fishuser.lv)-1)*4;
    p.xxl=p.xxl+st.xxl+baitCoe+fishuser.lv-1;
    p.xl=p.xl+st.xl+baitCoe+fishuser.lv-1;
    p.l=p.l+st.l+baitCoe+fishuser.lv-1;
    p.m=p.m+st.m+fishuser.lv-1;
    p.t=p.t+st.trash;
    p.s=st.xxl+st.xl+st.l+st.m-st.trash+((fishuser.lv-1)*4);
    let need={
        'success':(baitArr.indexOf(fishuser.Bait)+1)*100,  //미끼강화 : 낚시성공횟수
        'baitupgold':(baitArr.indexOf(fishuser.Bait)+1)*20000,  //미끼강화 : 미끼가격
        'rodupgold':Number(fishuser.lv)*config.rodupPrice,  //낚싯대 강화 필요금액
        'rodpopgold':Number(fishuser.rodpop)*config.rodpopPriceCoe+config.rodpopPrice  //낚싯대 뽑기 필요금액
    };
    if(fishuser.count.true==0) truep=0;
    else truep=(100/(Number(fishuser.count.total)/Number(fishuser.count.true)));
    replier.reply([
    '🎣 <'+fishuser.rank+'> '+fishuser.name,ll,
    '• 체력 : '+fishuser.hp+'/'+fishuser.maxHp,
    '• 낚싯대 : '+fishuser.item+'(Lv.'+fishuser.lv+')',
    '• 미끼 : '+fishuser.Bait+' (개당 '+(baitArr.indexOf(fishuser.Bait)*7+1)*config.baitPrice+'골드)',
    '• 미끼갯수 : '+fishuser.bait+'개 ('+fishuser.bait+'/'+config.maxBait+')',
    '• 보유금 : '+Div(fishuser.gold)+'골드'+more,,
    ii,'√ 기록 정보',ii,
    '• 낚시횟수 : '+fishuser.count.total+'회',
    '• 성공횟수 : '+fishuser.count.true+'회',
    '• 실패횟수 : '+fishuser.count.fail+'회',
    '• 누적성공률 : '+truep.toFixed(1)+'%',
    '• 최고기록'+(fishuser.best.cm==0?
        ' : '+fishuser.best.name:'\n'+fishuser.best.name+' ('+Div(fishuser.best.cm)+'Cm)'),,
    ii,'√ 세부 정보',ii,
    '• 휴식시간 : '+(Number(fishuser.rest)/1000)+'초당 체력 +1',
    '• 낚시소요시간 : '+(config.castT.min-fishuser.castT/1000)+'~'+(config.castT.max-fishuser.castT/1000)+'초 ('+(fishuser.castT/1000)+'초 단축)',
    '• 낚시성공률 : '+(65+Number(fishuser.successRate))+'% (+'+fishuser.successRate+'%)',
    '• 전설어 낚을 확률 : '+p.xxl/10+'% ('+(fishuser.stat.xxl+baitCoe+fishuser.lv-1>=0?'+':'')+(Number(fishuser.stat.xxl+baitCoe+fishuser.lv-1)/10)+'%)',
    '• 초대어 낚을 확률 : '+p.xl/10+'% ('+(fishuser.stat.xl+baitCoe+fishuser.lv-1>=0?'+':'')+(Number(fishuser.stat.xl+baitCoe+fishuser.lv-1)/10)+'%) - '+xl,
    '• 대어 낚을 확률 : '+p.l/10+'% ('+(fishuser.stat.l+baitCoe+fishuser.lv-1>=0?'+':'')+(Number(fishuser.stat.l+baitCoe+fishuser.lv-1)/10)+'%) - '+l,
    '• 중어 낚을 확률 : '+p.m/10+'% ('+(fishuser.stat.m+fishuser.lv-1>=0?'+':'')+(Number(fishuser.stat.m+fishuser.lv-1)/10)+'%) - '+m,
    '• 소어 낚을 확률 : '+(sfish<0?'0':sfish/10)+'% ('+(p.s*(-1)>=0?'+':'')+p.s/10*(-1)+'%)',
    '• 쓰레기 낚을 확률 : '+(p.t<0?'0':p.t/10)+'% ('+(fishuser.stat.trash>=0?'+':'')+Number(fishuser.stat.trash)/10+'%) - '+trash,,
    ll,'√ 기타 정보',ll,
    '• 낚싯대뽑기 비용 : '+Div(need.rodpopgold)+'골드',
    '• 낚싯대강화 비용 : '+(fishuser.lv==config.maxRodup?'최대강화 완료':Div(need.rodupgold)+'골드'),
    '• 미끼강화 비용 : '+((baitArr.indexOf(fishuser.Bait)+1==baitArr.length)?'최대강화 완료':('낚시성공횟수 '+Div(need.success)+'회, '+Div(need.baitupgold)+'골드'))
    ].join('\n'));
}

/* 유저정보 */
if(msg==fishcmd.info){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    UserInfo(fishUser[s]);
    return;
}

/* 상대방정보 */
if(msg.startsWith(fishcmd.info+' ')){
    let u=msg.substr(fishcmd.info.length+2);
    fishUser[u]=JSON.parse(FS.read(path+u+"/"+u+'.json'));
    if(fishUser[u]==null){
        replier.reply('@'+s+' \n• 정보를 찾지 못했어요.');
        return;
    }
    UserInfo(fishUser[u]);
    return;
}

/* 낚시통 */
if(msg==fishcmd.box){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    update(fishUser[s]);
    fishInv[s]=JSON.parse(FS.read(path+s+'/inv.json'));
    replier.reply([
        '🪣 <'+fishUser[s].rank+'> '+fishUser[s].name+'님의 낚시통'+more,li,
        fishcmd.sell+' : 잠금 제외한 모든 물고기 판매',
        fishcmd.lock+' (숫자) : 판매하지 않을 물고기 선택',
        fishcmd.unlock+' (숫자) : 물고기 잠금 해제',
        '※ 숫자 연속선택 예시 >> ..잠금 2-5 (2~5번 물고기 잠금)',li,
        (fishInv[s].lock.length==0&&fishInv[s].fish.length==0) ? '※ 잡은 물고기가 없어요.' :
            (fishInv[s].lock.length==0 ? '' : '🔒 잠금된 물고기\n'+ll+'\n'+
            fishInv[s].lock.sort((a,b)=>b.size-a.size).map((e,i)=>++i+'. '+e.name+' ('+Div(e.size)+'Cm)').join('\n')+'\n'+li+'\n')+
            (fishInv[s].fish.length==0 ? '' : (
                fishInv[s].lock.length==0? '' : '🐟 판매 가능한 물고기\n'+ll+'\n')+
            fishInv[s].fish.sort((a,b)=>b.size-a.size).map((e,i)=>++i+'. '+e.name+' ('+Div(e.size)+'Cm)').join('\n'))
    ].join('\n'));
    FS.write(path+s+'/inv.json', JSON.stringify(fishInv[s]));
    return;
}

/* 낚시 랭킹, 순위표 */
if(msg==fishcmd.rank){
    let fishRank=JSON.parse(FS.read(pathRank));
    if(fishRank==null){
        let data=[];
        FS.write(pathRank, JSON.stringify(data));
        replier.reply('@'+s+' \n• 낚시랭킹 파일 생성완료!');
        return;
    }
    let arr=[];
    let result;
    if(fishRank.length==0||fishRank==undefined) result='※ 랭킹에 등록된 유저가 없어요.';
    else if(fishRank.length==1)
        result='1등. '+fishRank[0].fish.name+'('+Div(fishRank[0].fish.size)+'Cm)\n>> 유저명 : <'+fishRank[0].rank+'> '+fishRank[0].name;
    else{
        fishRank.sort((a,b)=>b.fish.size-a.fish.size);
        let n;
        if(fishRank.length<config.ranknum) n=fishRank.length;
        else n=config.ranknum;
        for(let i=0; i<n; i++) arr.push(fishRank[i]);
        result=arr.map((e,i)=>++i+'등. '+e.fish.name+'('+Div(e.fish.size)+'Cm)\n>> 유저명 : <'+e.rank+'> '+e.name).join('\n'+ll+'\n');
    }
    replier.reply([
        '🐠 낚시 랭킹 (순위표)'+more,li,
        result
    ].join('\n'));
    return;
}

/* 칭호목록 출력 */
if(msg==fishcmd.title){
    replier.reply([
        '🎖 세라낚시터 칭호 목록'+more,li,
        config.rank.name.map((e,i)=>[
            '• 칭호명 : '+e,
            '• 조건 : 낚시성공 '+Div(config.rank.if[i])+'회 도달',
            '• 낚시성공률 : +'+config.rank.success[i]+'%',
            '• 최대체력 : '+config.rank.maxHp[i],
            '• 휴식중 '+(config.rank.rest[i]/1000)+'초당 체력 1 회복',
            '• 낚시 소요시간 '+(config.rank.castT[i]/1000)+'초 단축'
        ].join('\n')).join('\n'+ll+'\n')
    ].join('\n'));
    return;
}

/* 낚싯대 이름 뽑기 */
if(msg==fishcmd.rodnamepop){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    if(fishUser[s].gold<config.rodnamePrice){
        let gold=config.rodnamePrice-Number(fishUser[s].gold);
        replier.reply('@'+s+' \n• '+Div(gold)+'골드가 부족해요.');
        return;
    }
    update(fishUser[s]);
    let roded=fishUser[s].item;  //이전 낚싯대 이름
    let rod=rodArr[Math.random()*rodArr.length|0];  //뽑은 낚싯대 이름
    let ltt=roded.length-1;
    let lt=rod.length-1;
    fishUser[s].item=rod;
    fishUser[s].gold=Number(fishUser[s].gold)-config.rodnamePrice;
    FS.write(path+s+"/"+s+'.json', JSON.stringify(fishUser[s]));
    replier.reply('@'+s+' \n• '+Div(config.rodnamePrice)+'골드를 사용하여 "'+
    roded+(roded[ltt].normalize("NFD").length == 3 ? '"을' : '"를')+' "'+
    rod+(rod[lt].normalize("NFD").length==3 ? '"으로' : '"로')+' 변경 완료!');
    return;
}

/* 낚싯대 뽑기 */
if(msg==fishcmd.rodpop){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    update(fishUser[s]);
    let money=Number(fishUser[s].rodpop)*config.rodpopPriceCoe+config.rodpopPrice;  //뽑기비용계산
    if(fishUser[s].gold<money){
        let gold=money-Number(fishUser[s].gold);
        replier.reply('@'+s+' \n• '+Div(gold)+'골드가 부족해요.');
        return;
    }
    let rod=rodArr[Math.random()*rodArr.length|0];  //낚싯대이름
    let xl=Math.random()*32-2|0;  //-2 ~ 30
    let l=Math.random()*110-10|0;  //-10 ~ 100
    let m=Math.random()*250-50|0;  //-50 ~ 200
    let trash=Math.random()*200-150|0;  //-150 ~ 50
    let lt=rod.length-1;
    let xln, ln, mn, trashn  //스탯등급
    if(xl>25) xln='(최상)';
    else if(xl>15) xln='(상)';
    else if(xl>3) xln='(중)';
    else xln='(하)';
    if(l>80) ln='(최상)';
    else if(l>50) ln='(상)';
    else if(l>20) ln='(중)';
    else ln='(하)';
    if(m>170) mn='(최상)';
    else if(m>100) mn='(상)';
    else if(m>20) mn='(중)';
    else mn='(하)';
    if(trash<=-120) trashn='(최상)';
    else if(trash<=-70) trashn='(상)';
    else if(trash<=0) trashn='(중)';
    else trashn='(하)';
    fishUser[s].gold=Number(fishUser[s].gold)-money;
    fishUser[s].item=rod;
    fishUser[s].rodpop++;
    fishUser[s].stat.xl=Number(xl);
    fishUser[s].stat.l=Number(l);
    fishUser[s].stat.m=Number(m);
    fishUser[s].stat.trash=Number(trash);
    FS.write(path+s+"/"+s+'.json', JSON.stringify(fishUser[s]));
    replier.reply([
        '@'+s+' ',
        '• '+Div(money)+'골드를 사용하여 "'+rod+(rod[lt].normalize("NFD").length == 3 ? '"을' : '"를')+' 뽑았어요.',
        '• 남은금액 : '+Div(fishUser[s].gold)+'골드',
        '• 다음 뽑기 비용은 '+Div(Number(money)+config.rodpopPriceCoe)+'골드예요.'+more,,
        ll,'√ '+rod+' 능력치',ll,
        '• 쓰레기 낚을 확률 : '+(trash>=0?'+':'')+(Number(trash)/10)+'% '+trashn,
        '• 중어 낚을 확률 : '+(m>=0?'+':'')+(Number(m)/10)+'% '+mn,
        '• 대어 낚을 확률 : '+(l>=0?'+':'')+(Number(l)/10)+'% '+ln,
        '• 초대어 낚을 확률 : '+(xl>=0?'+':'')+(Number(xl)/10)+'% '+xln
    ].join('\n'));
    return;
}

/* 낚싯대 강화 */
if(msg==fishcmd.rodup){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    if(fishUser[s].lv==config.maxRodup){
        replier.reply('@'+s+' \n• 낚싯대 레벨이 최대예요.');
        return;
    }
    update(fishUser[s]);
    let gold=Number(fishUser[s].lv)*config.rodupPrice;  //필요금액
    if(fishUser[s].gold<gold){
        replier.reply('@'+s+' \n• '+Div(gold-Number(fishUser[s].gold))+'골드가 부족해요.');
        return;
    }
    let golded=Number(fishUser[s].gold);  //원래 보유금액
    fishUser[s].gold=Number(fishUser[s].gold)-gold;  //골드차감
    fishUser[s].lv++;  //레벨증가
    FS.write(path+s+"/"+s+'.json', JSON.stringify(fishUser[s]));
    replier.reply('@'+s+' \n• '+Div(gold)+'골드를 사용하여 "'+fishUser[s].item+'(Lv.'+(Number(fishUser[s].lv)-1)+' ➡ Lv.'+Number(fishUser[s].lv)+')" 로 증가되었어요.\n'+
        '• 보유금액 : '+Div(fishUser[s].gold)+'골드');
    return;
}

/* 미끼강화 */
if(msg==fishcmd.baitup){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    if(baitArr.indexOf(fishUser[s].Bait)==baitArr.length-1){
        replier.reply('@'+s+' \n• 미끼등급이 최대예요.');
        return;
    }
    let needs=(baitArr.indexOf(fishUser[s].Bait)+1)*100;  //필요 낚시성공횟수
    let gold=(baitArr.indexOf(fishUser[s].Bait)+1)*20000;  //미끼가격 계산
    let success=Number(fishUser[s].count.true);  //낚시성공횟수
    if(fishUser[s].gold<gold || success<needs){
        replier.reply('@'+s+(fishUser[s].gold<gold?' \n• '+Div(gold-fishUser[s].gold)+'골드가 부족해요.':'')+
            (success<needs?' \n• 낚시성공 '+(needs-success)+'회가 부족해요.':''));
        return;
    }
    let baited=fishUser[s].Bait;
    fishUser[s].gold=Number(fishUser[s].gold)-gold;
    fishUser[s].count.true=Number(fishUser[s].count.true)-needs;
    fishUser[s].count.total=Number(fishUser[s].count.total)-needs;
    fishUser[s].Bait=baitArr[baitArr.indexOf(fishUser[s].Bait)+1];
    FS.write(path+s+"/"+s+'.json', JSON.stringify(fishUser[s]));
    replier.reply('@'+s+' \n• '+Div(gold)+'골드, 성공횟수 '+Div(needs)+'회 차감.\n'+
        '• 미끼 : '+baited+' ➡ '+fishUser[s].Bait);
    return;
}

/* 회복, 휴식 */
if(msg==fishcmd.rest){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    if(fishUser[s].hp>=fishUser[s].maxHp){
        replier.reply('@'+s+' \n• 체력이 최대예요.');
        return;
    }
    update(fishUser[s]);
    if(!fishUser[s].restOn.on){
        fishUser[s].restOn.time=Number(Date.now());
        fishUser[s].restOn.on=1;
        FS.write(path+s+"/"+s+'.json', JSON.stringify(fishUser[s]));
        replier.reply('@'+s+' \n• 휴식을 시작합니다.\n• 중지하시려면 명령어를 다시 입력하세요.');
    } else{
        let time=Number(Date.now());
        time=parseInt((time-Number(fishUser[s].restOn.time))/Number(fishUser[s].rest));
        time=Number(time);
        let hp=Number(fishUser[s].hp);
        fishUser[s].hp=hp+Number(time);
        if(fishUser[s].hp>fishUser[s].maxHp) fishUser[s].hp=Number(fishUser[s].maxHp);
        fishUser[s].restOn.on=0;
        FS.write(path+s+"/"+s+'.json', JSON.stringify(fishUser[s]));
        replier.reply('@'+s+' \n• 휴식을 종료했어요.\n• 체력 '+hp+'/'+fishUser[s].maxHp+' ➡ '+fishUser[s].hp+'/'+fishUser[s].maxHp);
    }
    return;
}

/* 미끼구매 */
if(msg.startsWith(fishcmd.bait)){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    let n=msg.split(' ')[1];
    if(n==undefined) n=1;
    if(n<1){
        replier.reply('@'+s+' \n• 0과 음수는 기입할 수 없어요.');
        return;
    }
    if(isNaN(n)){
        replier.reply('@'+s+' \n• 갯수는 숫자로만 적어주세요.');
        return;
    }
    n=parseInt(n);
    if(fishUser[s].bait+n>config.maxBait){
        replier.reply('@'+s+' \n• 미끼는 최대 '+config.maxBait+'개까지 보유가능해요.\n• 현재 미끼갯수 : '+fishUser[s].bait+'개');
        return;
    }
    update(fishUser[s]);
    // (index*7+1)*baitPrice=미끼가격
    let gold=(baitArr.indexOf(fishUser[s].Bait)*7+1)*config.baitPrice*n;
    if(fishUser[s].gold<gold) replier.reply('@'+s+' \n• '+Div(gold-Number(fishUser[s].gold))+'골드가 부족해요.');
    else{
        fishUser[s].bait=Number(fishUser[s].bait)+n;
        fishUser[s].gold=Number(fishUser[s].gold)-gold;
        FS.write(path+s+"/"+s+'.json', JSON.stringify(fishUser[s]));
        replier.reply('@'+s+' \n• '+Div(gold)+'골드로 미끼 '+n+'개를 구매했어요.\n• 남은금액 : '+Div(fishUser[s].gold)+'골드');
    }
    return;
}

/* 물고기판매 */
if(msg==fishcmd.sell){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    fishInv[s]=JSON.parse(FS.read(path+s+'/inv.json'));
    if(fishInv[s].fish.length==0){
        replier.reply('@'+s+' \n• 판매할 물고기가 없어요.');
        return;
    }
    update(fishUser[s]);
    let sell=0;  //판매가격 합계
    let n=fishInv[s].fish.length;
    let gold=Number(fishUser[s].gold);  //이전 보유금액
    for(let i=0; i<fishInv[s].fish.length; i++)
        sell+=Number(fishInv[s].fish[i].gold);
    fishUser[s].gold=gold+sell;
    fishInv[s].fish=[];
    FS.write(path+s+"/"+s+'.json', JSON.stringify(fishUser[s]));
    FS.write(path+s+'/inv.json', JSON.stringify(fishInv[s]));
    replier.reply('@'+s+' \n'+
        '• 물고기 '+n+'마리를 '+Div(sell)+'골드에 팔았어요.\n'+
        '• '+Div(gold)+'골드 ➡ '+Div(fishUser[s].gold)+'골드');
    return;
}

/* 물고기 잠금 */
if(msg.startsWith(fishcmd.lock+' ')){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    fishInv[s]=JSON.parse(FS.read(path+s+'/inv.json'));
    if(fishInv[s].lock.length==0 && fishInv[s].fish.length==0){
        replier.reply('@'+s+' \n• 낚시통에 물고기가 없어요.');
        return;
    }
    if(fishInv[s].lock.length>0 && fishInv[s].fish.length==0){
        replier.reply('@'+s+' \n• 모든 물고기가 잠겨있어요.');
        return;
    }
    let n=msg.split(' ')[1];
    if(n.includes('-')&&!isNaN(n.split('-')[0])&&!isNaN(n.split('-')[1])){
        let x=Number(n.split('-')[0]);
        let z=Number(n.split('-')[1]);
        let num=z-x;
        if(num<0 || x<1 || z<1){
            replier.reply('@'+s+' \n• 입력값이 1 미만이거나, 순서가 바뀌었어요.');
            return;
        }
        if(fishInv[s].fish.length<z){
            replier.reply('@'+s+' \n• 입력범위를 벗어났어요.');
            return;
        }
        let box=[];
        for(let i=0; i<num+1; i++){
            let a=fishInv[s].fish.splice((x-1),1);  //물고기 뽑아오기
            fishInv[s].lock.push(a[0]);
            box.push(a[0]);
        }
        FS.write(path+s+'/inv.json', JSON.stringify(fishInv[s]));
        replier.reply([
            '@'+s+' ',
            '• 아래 물고기들을 잠금완료 했어요.'+more,li,
            box.map(e=>'• '+e.name+'('+Div(e.size)+'Cm)').join('\n')
        ].join('\n'));
        return;
    }
    if(n==undefined || isNaN(n)){
        replier.reply('@'+s+' \n• "'+fishcmd.lock+' (숫자)" 형식으로 적어주세요.');
        return;
    }
    if(n<=0){
        replier.reply('@'+s+' \n• 숫자는 0보다 커야해요.');
        return;
    }
    if(n>fishInv[s].fish.length){
        replier.reply('@'+s+' \n• 선택범위를 벗어났어요.');
        return;
    }
    n=parseInt(n);
    let a=fishInv[s].fish.splice((Number(n)-1),1);  //물고기 뽑아오기
    fishInv[s].lock.push(a[0]);
    let lt=a[0].name.length-1;
    FS.write(path+s+'/inv.json', JSON.stringify(fishInv[s]));
    replier.reply('@'+s+' \n• '+a[0].name+'('+Div(a[0].size)+'Cm)'+
        (a[0].name[lt].normalize("NFD").length == 3 ? '을' : '를')+
        ' 잠금 완료했어요.');
    return;
}

/* 물고기 잠금해제 */
if(msg.startsWith(fishcmd.unlock+' ')){
    fishUser[s]=JSON.parse(FS.read(path+s+"/"+s+'.json'));
    if(fishUser[s]==null){
        replier.reply('@'+s+' \n• 낚시터 회원이 아니예요.\n• "'+fishcmd.join+'" 을 먼저 해주세요.');
        return;
    }
    fishInv[s]=JSON.parse(FS.read(path+s+'/inv.json'));
    if(fishInv[s].lock.length==0 && fishInv[s].fish.length==0){
        replier.reply('@'+s+' \n• 낚시통에 물고기가 없어요.');
        return;
    }
    if(fishInv[s].lock.length==0 && fishInv[s].fish.length>0){
        replier.reply('@'+s+' \n• 잠겨있는 물고기가 없어요.');
        return;
    }
    let n=msg.split(' ')[1];
    if(n.includes('-')&&!isNaN(n.split('-')[0])&&!isNaN(n.split('-')[1])){
        let x=Number(n.split('-')[0]);
        let z=Number(n.split('-')[1]);
        let num=z-x;
        if(num<0 || x<1 || z<1){
            replier.reply('@'+s+' \n• 입력값이 1 미만이거나, 순서가 바뀌었어요.');
            return;
        }
        if(fishInv[s].lock.length<z){
            replier.reply('@'+s+' \n• 입력범위를 벗어났어요.');
            return;
        }
        let box=[];
        for(let i=0; i<num+1; i++){
            let a=fishInv[s].lock.splice((x-1),1);  //물고기 뽑아오기
            fishInv[s].fish.push(a[0]);
            box.push(a[0]);
        }
        FS.write(path+s+'/inv.json', JSON.stringify(fishInv[s]));
        replier.reply([
            '@'+s+' ',
            '• 아래 물고기들을 잠금해제 했어요.'+more,li,
            box.map(e=>'• '+e.name+'('+Div(e.size)+'Cm)').join('\n')
        ].join('\n'));
        return;
    }
    if(n==undefined || isNaN(n)){
        replier.reply('@'+s+' \n• "'+fishcmd.unlock+' (숫자)" 형식으로 적어주세요.');
        return;
    }
    if(n<=0){
        replier.reply('@'+s+' \n• 숫자는 0보다 커야해요.');
        return;
    }
    if(n>fishInv[s].lock.length){
        replier.reply('@'+s+' \n• 선택범위를 벗어났어요.');
        return;
    }
    n=parseInt(n);
    let a=fishInv[s].lock.splice((Number(n)-1),1);  //물고기 뽑아오기
    fishInv[s].fish.push(a[0]);
    let lt=a[0].name.length-1;
    FS.write(path+s+'/inv.json', JSON.stringify(fishInv[s]));
    replier.reply('@'+s+' \n• '+a[0].name+'('+Div(a[0].size)+'Cm)'+
        (a[0].name[lt].normalize("NFD").length == 3 ? '을' : '를')+
        ' 잠금 해제 했어요.');
    return;
}

}

'카톡봇 프로그래밍 > 소스' 카테고리의 다른 글

수동학습 소스 (FileStream)  (1) 2020.09.20
채팅내역 저장/출력 (FileStream)  (0) 2020.09.20
Comments