OSDN Git Service

Suppress redundant access to BBS.
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / data / Village.java
1 /*
2  * Village
3  *
4  * License : The MIT License
5  * Copyright(c) 2008 olyutorskii
6  */
7
8 package jp.sfjp.jindolf.data;
9
10 import java.awt.image.BufferedImage;
11 import java.text.MessageFormat;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.LinkedList;
15 import java.util.List;
16 import java.util.Map;
17 import jp.sfjp.jindolf.util.GUIUtils;
18 import jp.sourceforge.jindolf.corelib.LandDef;
19 import jp.sourceforge.jindolf.corelib.VillageState;
20
21 /**
22  * いわゆる「村」。
23  */
24 public class Village{
25
26     private static final int GID_MIN = 3;
27
28
29     private final Land parentLand;
30     private final String villageID;
31     private final int villageIDNum;
32     private final String villageName;
33
34     private final boolean isValid;
35
36     private int limitMonth;
37     private int limitDay;
38     private int limitHour;
39     private int limitMinute;
40
41     private VillageState state = VillageState.UNKNOWN;
42
43     private final LinkedList<Period> periodList = new LinkedList<>();
44     private final List<Period> unmodList =
45             Collections.unmodifiableList(this.periodList);
46
47     private final Map<String, Avatar> avatarMap =
48             new HashMap<>();
49
50     private final Map<Avatar, BufferedImage> faceImageMap =
51             new HashMap<>();
52     private final Map<Avatar, BufferedImage> bodyImageMap =
53             new HashMap<>();
54     private final Map<Avatar, BufferedImage> faceMonoImageMap =
55             new HashMap<>();
56     private final Map<Avatar, BufferedImage> bodyMonoImageMap =
57             new HashMap<>();
58
59
60     /**
61      * Villageを生成する。
62      *
63      * @param parentLand Villageの所属する国
64      * @param villageID 村のID
65      * @param villageName 村の名前
66      */
67     public Village(Land parentLand, String villageID, String villageName) {
68         this.parentLand    = parentLand;
69         this.villageID   = villageID.intern();
70         this.villageIDNum = Integer.parseInt(this.villageID);
71         this.villageName = villageName.intern();
72
73         this.isValid = this.parentLand.getLandDef()
74                        .isValidVillageId(this.villageIDNum);
75
76         return;
77     }
78
79
80     /**
81      * 所属する国を返す。
82      *
83      * @return 村の所属する国(Land)
84      */
85     public Land getParentLand(){
86         return this.parentLand;
87     }
88
89     /**
90      * 村のID文字列を返す。
91      *
92      * @return 村ID
93      */
94     public String getVillageID(){
95         return this.villageID;
96     }
97
98     /**
99      * 村のID数値を返す。
100      *
101      * @return 村ID
102      */
103     public int getVillageIDNum(){
104         return this.villageIDNum;
105     }
106
107     /**
108      * 村の名前を返す。
109      *
110      * @return 村の名前
111      */
112     public String getVillageName(){
113         StringBuilder name = new StringBuilder();
114
115         LandDef landDef = this.parentLand.getLandDef();
116         String prefix = landDef.getLandPrefix();
117         name.append(prefix);
118
119         StringBuilder id = new StringBuilder(this.villageID);
120         if(landDef.getLandId().equals("wolfg")){
121             while(id.length() < GID_MIN){
122                 id.insert(0, '0');
123             }
124         }
125         name.append(id);
126
127         String result = name.toString();
128         return result;
129     }
130
131     /**
132      * 村の長い名前を返す。
133      *
134      * @return 村の長い名前
135      */
136     public String getVillageFullName(){
137         return this.villageName;
138     }
139
140     /**
141      * 村の状態を返す。
142      *
143      * @return 村の状態
144      */
145     public VillageState getState(){
146         return this.state;
147     }
148
149     /**
150      * 村の状態を設定する。
151      *
152      * @param state 村の状態
153      */
154     public void setState(VillageState state){
155         this.state = state;
156         return;
157     }
158
159     /**
160      * 日程及び更新時刻を持っているか判定する。
161      *
162      * @return 日程が不明ならtrue
163      */
164     public boolean hasSchedule(){
165         boolean result = ! this.periodList.isEmpty();
166         return result;
167     }
168
169     /**
170      * プロローグを返す。
171      *
172      * @return プロローグ
173      */
174     public Period getPrologue(){
175         for(Period period : this.periodList){
176             if(period.isPrologue()) return period;
177         }
178         return null;
179     }
180
181     /**
182      * エピローグを返す。
183      *
184      * @return エピローグ
185      */
186     public Period getEpilogue(){
187         for(Period period : this.periodList){
188             if(period.isEpilogue()) return period;
189         }
190         return null;
191     }
192
193     /**
194      * 指定された日付の進行日を返す。
195      *
196      * @param day 日付
197      * @return Period
198      */
199     public Period getProgress(int day){
200         for(Period period : this.periodList){
201             if(    period.isProgress()
202                 && period.getDay() == day ) return period;
203         }
204         return null;
205     }
206
207     /**
208      * PROGRESS状態のPeriodの総数を返す。
209      *
210      * @return PROGRESS状態のPeriod総数
211      */
212     public int getProgressDays(){
213         int result = 0;
214         for(Period period : this.periodList){
215             if(period.isProgress()) result++;
216         }
217         return result;
218     }
219
220     /**
221      * 指定されたPeriodインデックスのPeriodを返す。
222      *
223      * <p>プロローグやエピローグへのアクセスも可能。
224      *
225      * @param day Periodインデックス
226      * @return Period
227      */
228     public Period getPeriod(int day){
229         return this.periodList.get(day);
230     }
231
232     /**
233      * 指定されたアンカーの対象のPeriodを返す。
234      *
235      * @param anchor アンカー
236      * @return Period
237      */
238     public Period getPeriod(Anchor anchor){
239         Period anchorPeriod;
240
241         if(anchor.isEpilogueDay()){
242             anchorPeriod = getEpilogue();
243             return anchorPeriod;
244         }
245
246         int anchorDay = anchor.getDay();
247         anchorPeriod = getPeriod(anchorDay);
248
249         return anchorPeriod;
250     }
251
252     /**
253      * Period総数を返す。
254      *
255      * @return Period総数
256      */
257     public int getPeriodSize(){
258         return this.periodList.size();
259     }
260
261     /**
262      * Periodへのリストを返す。
263      *
264      * @return Periodのリスト。
265      */
266     public List<Period> getPeriodList(){
267         return this.unmodList;
268     }
269
270     /**
271      * 指定した名前で村に登録されているAvatarを返す。
272      *
273      * @param fullName Avatarの名前
274      * @return Avatar
275      */
276     public Avatar getAvatar(String fullName){
277         // TODO CharSequenceにできない?
278         Avatar avatar;
279
280         avatar = Avatar.getPredefinedAvatar(fullName);
281         if( avatar != null ){
282             preloadAvatarFace(avatar);
283             return avatar;
284         }
285
286         avatar = this.avatarMap.get(fullName);
287         if( avatar != null ){
288             preloadAvatarFace(avatar);
289             return avatar;
290         }
291
292         return null;
293     }
294
295     /**
296      * Avatarの顔画像を事前にロードする。
297      *
298      * @param avatar Avatar
299      */
300     private void preloadAvatarFace(Avatar avatar){
301         if(this.faceImageMap.get(avatar) != null) return;
302
303         Land land = getParentLand();
304         LandDef landDef = land.getLandDef();
305
306         String template = landDef.getFaceURITemplate();
307         int serialNo = avatar.getIdNum();
308         String uri = MessageFormat.format(template, serialNo);
309
310         BufferedImage image = land.downloadImage(uri);
311         if(image == null) image = GUIUtils.getNoImage();
312
313         this.faceImageMap.put(avatar, image);
314
315         return;
316     }
317
318     /**
319      * Avatarを村に登録する。
320      *
321      * @param avatar Avatar
322      */
323     // 未知のAvatar出現時の処理が不完全
324     public void addAvatar(Avatar avatar){
325         if(avatar == null) return;
326         String fullName = avatar.getFullName();
327         this.avatarMap.put(fullName, avatar);
328
329         preloadAvatarFace(avatar);
330
331         return;
332     }
333
334     /**
335      * 村に登録されたAvatarの顔イメージを返す。
336      *
337      * @param avatar Avatar
338      * @return 顔イメージ
339      */
340     // TODO 失敗したらプロローグを強制読み込みして再トライしたい
341     public BufferedImage getAvatarFaceImage(Avatar avatar){
342         return this.faceImageMap.get(avatar);
343     }
344
345     /**
346      * 村に登録されたAvatarの全身像イメージを返す。
347      *
348      * @param avatar Avatar
349      * @return 全身イメージ
350      */
351     public BufferedImage getAvatarBodyImage(Avatar avatar){
352         BufferedImage result;
353         result = this.bodyImageMap.get(avatar);
354         if(result != null) return result;
355
356         Land land = getParentLand();
357         LandDef landDef = land.getLandDef();
358
359         String template = landDef.getBodyURITemplate();
360         int serialNo = avatar.getIdNum();
361         String uri = MessageFormat.format(template, serialNo);
362
363         result = land.downloadImage(uri);
364         if(result == null) result = GUIUtils.getNoImage();
365
366         this.bodyImageMap.put(avatar, result);
367
368         return result;
369     }
370
371     /**
372      * 村に登録されたAvatarのモノクロ顔イメージを返す。
373      *
374      * @param avatar Avatar
375      * @return 顔イメージ
376      */
377     public BufferedImage getAvatarFaceMonoImage(Avatar avatar){
378         BufferedImage result;
379         result = this.faceMonoImageMap.get(avatar);
380         if(result == null){
381             result = getAvatarFaceImage(avatar);
382             result = GUIUtils.createMonoImage(result);
383             this.faceMonoImageMap.put(avatar, result);
384         }
385         return result;
386     }
387
388     /**
389      * 村に登録されたAvatarの全身像イメージを返す。
390      *
391      * @param avatar Avatar
392      * @return 全身イメージ
393      */
394     public BufferedImage getAvatarBodyMonoImage(Avatar avatar){
395         BufferedImage result;
396         result = this.bodyMonoImageMap.get(avatar);
397         if(result == null){
398             result = getAvatarBodyImage(avatar);
399             result = GUIUtils.createMonoImage(result);
400             this.bodyMonoImageMap.put(avatar, result);
401         }
402         return result;
403     }
404
405     /**
406      * 国に登録された墓イメージを返す。
407      *
408      * @return 墓イメージ
409      */
410     public BufferedImage getGraveImage(){
411         BufferedImage result = getParentLand().getGraveIconImage();
412         return result;
413     }
414
415     /**
416      * 国に登録された墓イメージ(大)を返す。
417      *
418      * @return 墓イメージ(大)
419      */
420     public BufferedImage getGraveBodyImage(){
421         BufferedImage result = getParentLand().getGraveBodyImage();
422         return result;
423     }
424
425     /**
426      * 村にアクセスするためのCGIクエリーを返す。
427      *
428      * @return CGIクエリー
429      */
430     public String getCGIQuery(){
431         StringBuilder result = new StringBuilder();
432         result.append("?vid=").append(getVillageID());
433         return result.toString();
434     }
435
436     /**
437      * 次回更新時を設定する。
438      *
439      * @param month 月
440      * @param day 日
441      * @param hour 時
442      * @param minute 分
443      */
444     public void setLimit(int month, int day, int hour, int minute){
445         this.limitMonth = month;
446         this.limitDay = day;
447         this.limitHour = hour;
448         this.limitMinute = minute;
449         return;
450     }
451
452     /**
453      * 次回更新月を返す。
454      *
455      * @return 更新月(1-12)
456      */
457     public int getLimitMonth(){
458         return this.limitMonth;
459     }
460
461     /**
462      * 次回更新日を返す。
463      *
464      * @return 更新日(1-31)
465      */
466     public int getLimitDay(){
467         return this.limitDay;
468     }
469
470     /**
471      * 次回更新時を返す。
472      *
473      * @return 更新時(0-23)
474      */
475     public int getLimitHour(){
476         return this.limitHour;
477     }
478
479     /**
480      * 次回更新分を返す。
481      *
482      * @return 更新分(0-59)
483      */
484     public int getLimitMinute(){
485         return this.limitMinute;
486     }
487
488     /**
489      * 有効な村か否か判定する。
490      *
491      * @return 無効な村ならfalse
492      */
493     public boolean isValid(){
494         return this.isValid;
495     }
496
497     /**
498      * Periodリストの指定したインデックスにPeriodを上書きする。
499      *
500      * <p>リストのサイズと同じインデックスを指定する事が許される。
501      * その場合の動作はList.addと同じ。
502      *
503      * @param index Periodリストのインデックス。
504      * @param period 上書きするPeriod
505      * @throws java.lang.IndexOutOfBoundsException インデックスの指定がおかしい
506      */
507     public void setPeriod(int index, Period period)
508             throws IndexOutOfBoundsException{
509         int listSize = this.periodList.size();
510         if(index == listSize){
511             this.periodList.add(period);
512         }else if(index < listSize){
513             this.periodList.set(index, period);
514         }else{
515             throw new IndexOutOfBoundsException();
516         }
517         return;
518     }
519
520     /**
521      * アンカーに一致する会話(Talk)のリストを取得する。
522      *
523      * @param anchor アンカー
524      * @return Talkのリスト
525      */
526     public List<Talk> getTalkListFromAnchor(Anchor anchor){
527         List<Talk> result = new LinkedList<>();
528
529         /* G国アンカー対応 */
530         if(anchor.hasTalkNo()){
531             // 事前に全Periodの全会話がロードされているのが前提
532             for(Period period : this.periodList){
533                 Talk talk = period.getNumberedTalk(anchor.getTalkNo());
534                 if(talk == null) continue;
535                 result.add(talk);
536             }
537             return result;
538         }
539
540         Period anchorPeriod = getPeriod(anchor);
541         if(anchorPeriod == null) return result;
542
543         // 事前にアンカー対象Periodの全会話がロードされているのが前提
544
545         for(Topic topic : anchorPeriod.getTopicList()){
546             if( ! (topic instanceof Talk) ) continue;
547             Talk talk = (Talk) topic;
548             if(talk.getHour()   != anchor.getHour()  ) continue;
549             if(talk.getMinute() != anchor.getMinute()) continue;
550             result.add(talk);
551         }
552         return result;
553     }
554
555     /**
556      * 全Periodの発言データをアンロードする。
557      */
558     public void unloadPeriods(){
559         for(Period period : this.periodList){
560             period.unload();
561         }
562         return;
563     }
564
565     /**
566      * {@inheritDoc}
567      *
568      * <p>村の文字列表現を返す。
569      * 村の名前と等しい。
570      *
571      * @return 村の名前
572      */
573     @Override
574     public String toString(){
575         return getVillageFullName();
576     }
577
578 }