बारी-आधारित गेम का डिज़ाइन जहां क्रियाओं के दुष्प्रभाव होते हैं


19

मैं खेल डोमिनियन का एक कंप्यूटर संस्करण लिख रहा हूं । यह एक टर्न-आधारित कार्ड गेम है जहां एक्शन कार्ड, ट्रेजर कार्ड और जीत प्वाइंट कार्ड एक खिलाड़ी के व्यक्तिगत डेक में जमा होते हैं। मेरे पास वर्ग संरचना बहुत अच्छी तरह से विकसित है, और मैं गेम लॉजिक डिजाइन करना शुरू कर रहा हूं। मैं अजगर का उपयोग कर रहा हूं, और मैं बाद में अजगर के साथ एक साधारण जीयूआई जोड़ सकता हूं।

खिलाड़ियों का टर्न सीक्वेंस एक बहुत ही साधारण स्टेट मशीन द्वारा शासित होता है। क्लॉकवाइज पास हो जाता है, और एक खिलाड़ी खेल खत्म होने से पहले बाहर नहीं निकल सकता। एक एकल मोड़ का खेल भी एक राज्य मशीन है; सामान्य तौर पर, खिलाड़ी एक "एक्शन चरण", एक "खरीद चरण" और "क्लीन-अप चरण" (उस क्रम में) से गुजरते हैं। प्रश्न के उत्तर के आधार पर टर्न-आधारित गेम इंजन को कैसे लागू किया जाए? राज्य मशीन इस स्थिति के लिए एक मानक तकनीक है।

मेरी समस्या यह है कि किसी खिलाड़ी के एक्शन चरण के दौरान, वह ऐसे एक्शन कार्ड का उपयोग कर सकता है, जिसके दुष्प्रभाव या तो स्वयं पर हों, या एक या एक से अधिक खिलाड़ियों पर हों। उदाहरण के लिए, एक एक्शन कार्ड एक खिलाड़ी को वर्तमान मोड़ के समापन के तुरंत बाद दूसरा मोड़ लेने की अनुमति देता है। एक अन्य एक्शन कार्ड के कारण अन्य सभी खिलाड़ी अपने हाथों से दो कार्ड छोड़ सकते हैं। एक और एक्शन कार्ड वर्तमान मोड़ के लिए कुछ भी नहीं करता है, लेकिन एक खिलाड़ी को उसके अगले मोड़ पर अतिरिक्त कार्ड खींचने की अनुमति देता है। चीजों को और अधिक जटिल बनाने के लिए, खेल में अक्सर नए विस्तार होते हैं जो नए कार्ड जोड़ते हैं। मुझे ऐसा लगता है कि गेम की स्टेट मशीन में हर एक्शन कार्ड के परिणाम को हार्ड-कोडिंग करना बदसूरत और असाध्य दोनों होगा। टर्न-आधारित रणनीति लूप का जवाब विस्तार के एक स्तर पर नहीं जाता है जो इस समस्या को हल करने के लिए डिजाइन को संबोधित करता है।

इस तरह के प्रोग्रामिंग मॉडल का उपयोग मुझे इस तथ्य को शामिल करने के लिए करना चाहिए कि टर्न लेने के लिए सामान्य पैटर्न को टर्न के भीतर होने वाली क्रियाओं द्वारा संशोधित किया जा सकता है? क्या गेम ऑब्जेक्ट को हर एक्शन कार्ड के प्रभावों का ट्रैक रखना चाहिए? या, यदि कार्डों को अपने स्वयं के प्रभाव को लागू करना चाहिए (जैसे एक इंटरफ़ेस को लागू करके), तो उन्हें पर्याप्त शक्ति देने के लिए क्या सेटअप आवश्यक है? मैंने इस समस्या का कुछ समाधान सोचा है, लेकिन मैं सोच रहा हूं कि क्या इसे हल करने का कोई मानक तरीका है। विशेष रूप से, मैं यह जानना चाहता हूं कि हर वस्तु को एक एक्शन कार्ड के रूप में किए जाने वाले कार्यों पर नज़र रखने के लिए क्या वस्तु / वर्ग / जो कुछ भी जिम्मेदार है, और यह भी कि सामान्य अनुक्रम में अस्थायी परिवर्तनों से संबंधित कैसे हैं। बारी राज्य मशीन।


2
नमस्ते एपिस यूटिलिस, और GDSE में आपका स्वागत है। आपका प्रश्न अच्छी तरह से लिखा गया है, और यह बहुत अच्छा है कि आपने संबंधित प्रश्नों का संदर्भ दिया। हालाँकि, आपका प्रश्न बहुत सी विभिन्न समस्याओं को कवर कर रहा है, और इसे पूरी तरह से कवर करने के लिए, एक प्रश्न को बहुत अधिक होने की आवश्यकता होगी। आपको अभी भी एक अच्छा जवाब मिल सकता है, लेकिन अगर आप अपनी समस्या को कुछ और दूर करते हैं तो खुद को और साइट को फायदा होगा। शायद एक सरल खेल के निर्माण के साथ शुरू करें और डोमिनियन तक का निर्माण करें?
michael.bartnett

1
मैं प्रत्येक कार्ड को एक स्क्रिप्ट देने से शुरू करता हूं जो खेल की स्थिति को संशोधित करता है, और अगर कुछ भी अजीब नहीं चल रहा है, तो डिफ़ॉल्ट मोड़ नियमों पर वापस आ जाओ ...
जरी कोम्पा

जवाबों:


11

मैं जरी कोम्पा से सहमत हूं कि एक शक्तिशाली स्क्रिप्टिंग भाषा के साथ कार्ड के प्रभावों को परिभाषित करने का तरीका है। लेकिन मेरा मानना ​​है कि अधिकतम लचीलेपन की कुंजी स्क्रिप्ट करने योग्य ईवेंट-हैंडलिंग है।

कार्ड को बाद के खेल की घटनाओं के साथ बातचीत करने की अनुमति देने के लिए, आप कुछ घटनाओं के लिए "स्क्रिप्ट हुक" जोड़ने के लिए एक स्क्रिप्टिंग एपीआई जोड़ सकते हैं, जैसे कि खेल चरणों की शुरुआत और अंत, या कुछ क्रियाओं में खिलाड़ी प्रदर्शन कर सकते हैं। इसका मतलब यह है कि जब कार्ड खेला जाता है तो जो स्क्रिप्ट निष्पादित होती है वह एक फ़ंक्शन को पंजीकृत करने में सक्षम होती है जिसे अगली बार एक विशिष्ट चरण कहा जाता है। प्रत्येक ईवेंट के लिए फ़ंक्शंस की संख्या असीमित होनी चाहिए। जब एक से अधिक होते हैं, तो उन्हें पंजीकरण के अपने आदेश में बुलाया जाता है (जब तक कि निश्चित रूप से एक कोर गेम नियम नहीं है जो कुछ अलग कहता है)।

सभी खिलाड़ियों के लिए या केवल कुछ खिलाड़ियों के लिए इन हुक को पंजीकृत करना संभव होना चाहिए। मैं खुद को तय करने के लिए हुक के लिए सकारात्मकता जोड़ने का भी सुझाव दूंगा कि उन्हें बुलाया जाना चाहिए या नहीं। इन उदाहरणों में इसे व्यक्त करने के लिए हुक फ़ंक्शन (सही या गलत) का रिटर्न मान उपयोग किया जाता है।

आपका डबल-टर्न कार्ड तब कुछ ऐसा करेगा:

add_event_hook('cleanup_phase_end', current_player, function {
     setNextPlayer(current_player); // make the player take another turn
     return false; // unregister this hook afterwards
});

(मुझे नहीं पता कि क्या डोमिनियन के पास भी "क्लीनअप चरण" जैसा कुछ है - इस उदाहरण में यह खिलाड़ियों के अंतिम चरण का काल्पनिक मोड़ है)

एक कार्ड जो प्रत्येक खिलाड़ी को अपने ड्रॉ चरण की शुरुआत में एक अतिरिक्त कार्ड बनाने की अनुमति देता है, वह इस तरह दिखाई देगा:

add_event_hook('draw_phase_begin', NULL, function {
    drawCard(current_player); // draw a card
    return true; // keep doing this until the hook is removed explicitely
});

एक कार्ड जो लक्ष्य खिलाड़ी को एक हिट बिंदु खो देता है जब भी कोई कार्ड खेलता है तो वह इस तरह दिखाई देगा:

add_event_hook('play_card', target_player, function {
    changeHitPoints(target_player, -1); // remove a hit point
    return true; 
});

आपको हार्ड-कोडिंग के बारे में कुछ गेम क्रियाएं नहीं मिलेंगी जैसे कि ड्रॉइंग कार्ड्स या हिट पॉइंट्स को खोना, क्योंकि उनकी पूरी परिभाषा - "कार्ड आकर्षित करने" का वास्तव में क्या मतलब है - कोर गेम मैकेनिक्स का हिस्सा है। उदाहरण के लिए, मुझे कुछ टीसीजी के बारे में पता है, जहां जब आपको किसी भी कारण से कार्ड खींचना होता है और आपका डेक खाली होता है, तो आप गेम खो देते हैं। यह नियम हर कार्ड पर मुद्रित नहीं होता है, जो आपको कार्ड बनाता है, क्योंकि यह नियम पुस्तिका में है। तो आपको हर कार्ड की स्क्रिप्ट में उस खोने की स्थिति की भी जांच नहीं करनी चाहिए। इस तरह की चीजों की जांच करना हार्ड-कोडेड drawCard()फ़ंक्शन का हिस्सा होना चाहिए (जो, वैसे, एक हुक करने योग्य घटना के लिए एक अच्छा उम्मीदवार होगा)।

वैसे: यह संभावना नहीं है कि आप हर अस्पष्ट मैकेनिक भविष्य के संस्करणों के लिए आगे की योजना बना पाएंगे , इसलिए कोई फर्क नहीं पड़ता कि आप क्या करते हैं, आपको अभी भी भविष्य के संस्करणों के लिए एक बार में नई कार्यक्षमता जोड़ना होगा (इसमें मामला, एक कंफ़ेद्दी फेंकने वाले मिनीगेम)।


1
वाह। वह अराजकता कंफ़ेद्दी चीज़।
जरी कोमप्पा

उत्कृष्ट उत्तर, @Philipp, और यह डोमिनियन में किए गए बहुत सारे कामों का ध्यान रखता है। हालाँकि, ऐसी क्रियाएं होती हैं जो एक कार्ड बजने पर तुरंत होनी चाहिए, यानी एक कार्ड खेला जाता है जो किसी अन्य खिलाड़ी को उसके पुस्तकालय के शीर्ष कार्ड को चालू करने के लिए मजबूर करता है और वर्तमान खिलाड़ी को "इसे रखें" या "इसे त्यागें" कहने की अनुमति देता है। क्या आप ऐसी तात्कालिक कार्रवाइयों का ध्यान रखने के लिए ईवेंट हुक लिखेंगे, या आपको कार्ड की स्क्रिप्टिंग के अतिरिक्त तरीकों के साथ आने की आवश्यकता होगी?
fnord

2
जब कुछ तुरंत होने की आवश्यकता होती है, तो स्क्रिप्ट को उचित कार्यों को सीधे कॉल करना चाहिए और हुक फ़ंक्शन को पंजीकृत नहीं करना चाहिए।
फिलिप

@ जारीकोम्पा: अनलग्ड सेट जानबूझकर निरर्थक था और पागल कार्ड से भरा हुआ था जिसका कोई मतलब नहीं था। मेरा पसंदीदा एक कार्ड था जिसने सभी को एक विशेष शब्द कहने पर नुकसान का एक बिंदु बना दिया। मैंने 'द' को चुना।
जैक ऐडली

9

मैंने इस समस्या को दिया - लचीला कम्प्यूटरीकृत कार्ड गेम इंजन - कुछ समय पहले कुछ सोचा था।

सबसे पहले, Chez Geek या Fluxx (और, मुझे विश्वास है, डोमिनियन) जैसे जटिल कार्ड गेम को कार्ड को स्क्रिप्ट करने योग्य बनाने की आवश्यकता होगी। मूल रूप से प्रत्येक कार्ड स्क्रिप्ट के अपने स्वयं के गुच्छा के साथ आएगा जो विभिन्न तरीकों से खेल की स्थिति को बदल सकता है। इससे आप सिस्टम को कुछ भविष्य के प्रमाण दे सकते हैं, क्योंकि स्क्रिप्ट उन चीजों को करने में सक्षम हो सकती है जो आप अभी सोच नहीं सकते हैं, लेकिन भविष्य के विस्तार में आ सकते हैं।

दूसरा, कठोर "मोड़" समस्या पैदा कर सकता है।

आपको कुछ प्रकार के "टर्न स्टैक" की आवश्यकता होती है, जिसमें "विशेष घुमाव" होते हैं, जैसे "2 कार्ड को त्यागें"। जब स्टैक खाली होता है, तो डिफ़ॉल्ट सामान्य मोड़ जारी रहता है।

फ्लक्सक्स में, यह पूरी तरह से संभव है कि एक मोड़ कुछ इस तरह से हो:

  • एन कार्ड चुनें (वर्तमान नियमों के अनुसार, कार्ड के माध्यम से परिवर्तनशील)
  • एन कार्ड खेलें (वर्तमान नियमों के अनुसार, कार्ड के माध्यम से परिवर्तनशील)
    • कार्ड में से एक हो सकता है "ले 3, उनमें से 2 खेलते हैं"
      • उन कार्डों में से एक अच्छी तरह से "एक और मोड़ ले सकता है"
    • कार्ड में से एक "त्यागें और ड्रा" हो सकता है
  • यदि आप अपनी बारी शुरू होने से अधिक कार्ड लेने के लिए नियम बदलते हैं, तो अधिक कार्ड चुनें
  • यदि आप हाथ में कम कार्ड के लिए नियम बदलते हैं, तो बाकी सभी को तुरंत कार्ड छोड़ देना चाहिए
  • जब आपकी बारी समाप्त हो जाती है, तब तक कार्डों को त्याग दें जब तक आपके पास एन कार्ड (कार्ड के माध्यम से फिर से) नहीं हो जाते हैं, तब एक और मोड़ लें (यदि आपने उपरोक्त गड़बड़ में कुछ समय बाद "एक और मोड़" कार्ड खेला है)।

..इत्यादि इत्यादि। तो एक टर्न संरचना को डिजाइन करना जो उपरोक्त दुरुपयोग को संभाल सकता है बल्कि मुश्किल हो सकता है। कि "जब भी" कार्ड ("chez geek" की तरह) के साथ कई खेलों में जोड़ें, जहां "जब भी" कार्ड सामान्य प्रवाह को बाधित कर सकते हैं, उदाहरण के लिए, जो भी कार्ड खेला गया था उसे रद्द करना।

इसलिए मूल रूप से मैं एक बहुत ही लचीली टर्न संरचना को डिजाइन करने से शुरू करता हूं, इसे डिजाइन करता हूं ताकि इसे एक स्क्रिप्ट के रूप में वर्णित किया जा सके (जैसा कि प्रत्येक गेम को मूल गेम संरचना को संभालने के लिए अपने स्वयं के "मास्टर स्क्रिप्ट" की आवश्यकता होगी)। फिर, कोई भी कार्ड स्क्रिप्ट योग्य होना चाहिए; अधिकांश कार्ड शायद कुछ भी अजीब नहीं करते हैं, लेकिन अन्य करते हैं। कार्ड में विभिन्न विशेषताएं भी हो सकती हैं - चाहे उन्हें "जब भी" हाथ में रखा जा सके, चाहे उन्हें संपत्ति के रूप में संग्रहीत किया जा सकता है (जैसे फ्लक्सएक्स 'कीपर्स', या भोजन की तरह 'चीज़ गीक' में विभिन्न चीजें) ...

मैंने वास्तव में इसमें से किसी को भी लागू करना शुरू नहीं किया है, इसलिए व्यवहार में आपको बहुत सारी अन्य चुनौतियाँ मिल सकती हैं। शुरू करने का सबसे आसान तरीका यह होगा कि आप जिस भी सिस्टम के बारे में जानना चाहते हैं, उसके साथ शुरू करें और उन्हें स्क्रिप्ट करने योग्य तरीकों से लागू करें, जितना संभव हो उतना कम पत्थर की स्थापना, इसलिए जब एक विस्तार साथ आता है, तो आपको संशोधित करने की आवश्यकता नहीं होगी। आधार प्रणाली - बहुत। =)


यह एक महान जवाब है, और मुझे लगता है कि अगर मैं दोनों को स्वीकार कर सकता था। मैंने निचली प्रतिष्ठा वाले व्यक्ति द्वारा उत्तर स्वीकार कर टाई तोड़ दी :)
Apis Utilis


0

चूल्हा चीजों को भरोसेमंद और ईमानदारी से करता है मुझे लगता है कि लचीलेपन को प्राप्त करने का सबसे अच्छा तरीका एक डेटा उन्मुख डिजाइन के साथ ईसीएस इंजन है। एक चूल्हा क्लोन बनाने की कोशिश कर रहा है और यह अन्यथा असंभव साबित होता है। सभी किनारे मामले। यदि आपके इन अजीब तरह के मामलों का सामना करना पड़ रहा है, तो शायद इसके बारे में जाने का सबसे अच्छा तरीका है। हालांकि इस तकनीक को आज़माने के हाल के अनुभव से मैं बहुत पक्षपाती हूँ।

संपादित करें: ईसीएस को लचीलेपन के प्रकार और आपके इच्छित अनुकूलन के आधार पर भी आवश्यकता नहीं हो सकती है। इसे पूरा करने का सिर्फ एक तरीका है। डीओडी मैंने गलती से प्रक्रियात्मक प्रोग्रामिंग के रूप में सोचा था, हालांकि वे बहुत से संबंधित हैं। मेरा कहने का तात्पर्य है। आपको ओओपी के साथ पूरी तरह से या अधिकतर कम से कम दूर करने पर विचार करना चाहिए और इसके बजाय डेटा पर अपना ध्यान केंद्रित करना चाहिए और यह कैसे व्यवस्थित किया जाना चाहिए। विरासत और तरीकों से बचें। इसके बजाय अपने कार्ड डेटा में हेरफेर करने के लिए सार्वजनिक कार्यों (सिस्टम) पर ध्यान केंद्रित करें। प्रत्येक क्रिया किसी भी तरह की एक टेम्प्लेटेड चीज़ या तर्क नहीं है, बल्कि इसके बजाय कच्चे डेटा है। जहां आपके सिस्टम तब तर्क करने के लिए इसका उपयोग करते हैं। पूर्णांक स्विच मामले या फ़ंक्शन पॉइंटर्स की एक सरणी तक पहुंचने के लिए पूर्णांक का उपयोग करके इनपुट डेटा से वांछित तर्क को कुशलतापूर्वक निकालने में मदद करता है।

पालन ​​करने के लिए बुनियादी नियम हैं कि आपको डेटा के साथ तर्क को सीधे बांधने से बचना चाहिए, आपको डेटा को एक दूसरे पर जितना संभव हो सके निर्भर करने से बचना चाहिए (अपवाद लागू हो सकते हैं), और यह कि जब आपका वांछित लचीला तर्क जो पहुंच से बाहर लगता है ... इसे डेटा में परिवर्तित करने पर विचार करें।

ऐसा करने के लिए लाभ हैं। प्रत्येक कार्ड में उनकी कार्रवाई का प्रतिनिधित्व करने के लिए एक एनम (एस) मान या स्ट्रिंग (एस) हो सकता है। यह इंटर्न आपको टेक्स्ट या जोंस फाइलों के माध्यम से कार्ड डिजाइन करने और प्रोग्राम को स्वचालित रूप से आयात करने की अनुमति देता है। यदि आप खिलाड़ी क्रियाओं को डेटा की एक सूची बनाते हैं, तो यह और भी अधिक लचीलापन देता है, खासकर यदि कार्ड पिछले तर्क पर निर्भर करता है जैसे कि चूल्हा करता है, या यदि आप गेम को सहेजना चाहते हैं या किसी भी बिंदु पर गेम को खेलना चाहते हैं। एआई को आसानी से बनाने की क्षमता है। विशेष रूप से जब एक "व्यवहार पेड़" के बजाय "उपयोगिता प्रणाली" का उपयोग किया जाता है। नेटवर्किंग और भी आसान हो जाती है क्योंकि वायर पर ट्रांसफर करने के लिए पूरी तरह से बहुरूपी वस्तुओं को प्राप्त करने की आवश्यकता का पता लगाने के बजाय और इस तथ्य के बाद क्रमबद्धता कैसे सेट की जाएगी, आपके पहले से ही आपके खेल की वस्तुएं सरल डेटा से अधिक कुछ नहीं हैं जो समाप्त होता है वास्तव में चारों ओर घूमने में आसान होता है। और अंतिम लेकिन निश्चित रूप से कम से कम यह आपको आसानी से अनुकूलन करने की अनुमति देता है क्योंकि समय बर्बाद करने के बजाय कोड के बारे में चिंता करने से आपके डेटा को बेहतर ढंग से व्यवस्थित करने में सक्षम है, इसलिए प्रोसेसर के पास इससे निपटने का एक आसान समय होगा। अजगर के यहाँ मुद्दे हो सकते हैं लेकिन "कैश लाइन" देखें और यह कैसे खेल देव से संबंधित है। शायद प्रोटोटाइप के सामान के लिए महत्वपूर्ण नहीं है, लेकिन सड़क के नीचे यह बड़े समय में काम आएगा।

कुछ उपयोगी लिंक

नोट: ECS रनटाइम के दौरान डायनामिक रूप से वैरिएबल (घटक कहा जाता है) को जोड़ने / हटाने की अनुमति देता है। ईसीएस "कैसे दिख सकता है" का एक उदाहरण ग कार्यक्रम (इसमें करने के तरीकों के लिए एक टन है)।

unsigned int textureID = ECSRegisterComponent("texture", sizeof(struct Texture));
unsigned int positionID = ECSRegisterComponent("position", sizeof(struct Point2DI));
for (unsigned int i = 0; i < 10; i++) {
    void *newEnt = ECSGetNewEntity();
    struct Point2DI pos = { 0 + i * 64, 0 };
    struct Texture tex;
    getTexture("test.png", &tex);
    ECSAddComponentToEntity(newEnt, &pos, positionID);
    ECSAddComponentToEntity(newEnt, &tex, textureID);
}
void *ent = ECSGetParentEntity(textureID, 3);
ECSDestroyEntity(ent);

बनावट और स्थिति डेटा के साथ संस्थाओं का एक समूह बनाता है और अंत में एक इकाई को नष्ट कर देता है जिसके पास एक बनावट घटक होता है जो बनावट घटक सरणी के तीसरे सूचकांक में होता है। विचित्र लगता है लेकिन चीजों को करने का एक तरीका है। यहाँ एक उदाहरण दिया गया है कि आप किस तरह से सब कुछ प्रस्तुत करना चाहते हैं जिसमें बनावट घटक हो।

unsigned int textureCount;
unsigned int positionID = ECSGetComponentTypeFromName("position");
unsigned int textureID = ECSGetComponentTypeFromName("texture");
struct Texture *textures = ECSGetAllComponentsOfType(textureID, &textureCount);
for (unsigned int i = 0; i < textureCount; i++) {
    void *parentEntity = ECSGetParentEntity(textureID, i);
    struct Point2DI *drawPos = ECSGetComponentFromEntity(positionID, parentEntity);
    if (drawPos) {
        struct Texture *t = &textures[i];
        drawTexture(t, drawPos->x, drawPos->y);
    }
}

1
यह उत्तर बेहतर होगा यदि यह इस बारे में कुछ और विस्तार से बताता है कि आप अपने डेटा-उन्मुख ईसीएस को स्थापित करने और इसे विशिष्ट समस्या को हल करने के लिए इसे लागू करने की सिफारिश कैसे करेंगे।
DMGregory

अद्यतन करने के लिए धन्यवाद कि बाहर इशारा करते हैं।
ब्लू_पायरो

सामान्य तौर पर, मुझे लगता है कि इस तरह के दृष्टिकोण को स्थापित करने के लिए किसी को "कैसे" बताना बुरा है, लेकिन इसके बजाय उन्हें अपना समाधान डिज़ाइन करने दें। यह अभ्यास करने का एक अच्छा तरीका साबित होता है और समस्या के संभावित बेहतर समाधान की अनुमति देता है। जब इस तरीके से तर्क से अधिक डेटा के बारे में सोचा जा रहा है तो यह समाप्त होता है कि एक ही चीज़ को पूरा करने के कई तरीके हैं और यह सब आवेदन की जरूरतों पर निर्भर करता है। साथ ही प्रोग्रामर समय / ज्ञान।
ब्लू_पायरो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.